Diagnóstico y Evaluación de Modelos de Regresión Lineal

Dataset

Los datos con los que se trabajará en este TP provienen de la 3° Encuesta Mundial de Salud Escolar (EMSE) provistos por el Ministerio de Salud link de la República Argentina. Esta encuesta trata sobre temas de salud y hábitos de las personas en la escuela secundaria que pueden impactar en su salud.

rm( list=ls() )  #remove all objects
gc()       
          used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
Ncells  541512   29    1212557 64.8         NA   669240 35.8
Vcells 1037625    8    8388608 64.0      65536  4787859 36.6
options(scipen=999)
# Carga de librerías
library(corrr)
library(ggplot2)
library(dplyr)
library(tidyverse)
library(tidymodels)
library(rsample)
library(gridExtra)
library(knitr)
library(kableExtra)
library(GGally)

Comenzamos leyendo los datos y viendo su estructura, para esto utilizamos la función glimpse

#Importamos los datasets con los que trabajaremos
encuesta_salud_train = read.csv("/Users/dfontenla/Maestria/2022C2/EEA/practica/repo/EEA-2022/TP1/encuesta_salud_train.csv", encoding = "UTF-8") %>% mutate(id = 1:nrow(.)) 
encuesta_salud_test = read.csv("/Users/dfontenla/Maestria/2022C2/EEA/practica/repo/EEA-2022/TP1/encuesta_salud_test.csv", encoding = "UTF-8") %>% mutate(id = 1:nrow(.)) 
#encuesta_salud_train$nivel_educativo
#Observamos la estructura y variables
encuesta_salud_train %>% glimpse()
Rows: 7,024
Columns: 17
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, …
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4t…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca"…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 dí…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últ…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…

Existen 7024 registros, compuesto de 17 datos diferentes con los que trabajaremos para entrenar diferentes modelos y poder realizar la prediccion de la variable peso. Nos quedamos con todo el dataset, como se menciona en el enunciado, corresponden a un recorte (muestra) del dataset original, luego del tratamiento de valores atípicos e ingeniería de atributos.

Análisis exploratorios

Valores únicos y porcentaje de faltantes

tabla_exploratorios =  encuesta_salud_train %>%
                                      gather(., 
                                            key = "variables", 
                                            value = "valores") %>% # agrupamos por las variables del set
                                      group_by(variables) %>% 
                                      summarise(valores_unicos = n_distinct(valores),
                                      porcentaje_faltantes = sum(is.na(valores))/nrow(encuesta_salud_train)*100) %>% 
                                      arrange(desc(porcentaje_faltantes), valores_unicos) # ordenamos por porcentaje de faltantes y valores unicos
tabla_exploratorios

Observamos un dataset que ya esta curado de datos faltantes y su composición esta dada por una mayor cantidad de datos categóricos que continuos

Análisis descriptivo

Realizamos un primer análisis descriptivo de nuestro dataset de columnas relevantes. Para ello utilizamos la función ggpairs sobre las variables numéricas con una apertura por la variable de sexo.

#Observamos la correlacion de variables
#encuesta_salud_train_numeric = encuesta_salud_train %>% dplyr::select(where(is.numeric))

# Seleccionamos las variables de interés
encuesta_salud_train_gender_numeric = encuesta_salud_train %>%
                              dplyr::select(edad, genero, altura, peso,dias_consumo_comida_rapida, consumo_diario_alcohol, 
                              dias_actividad_fisica_semanal)

encuesta_salud_train_gender_numeric %>% glimpse()
Rows: 7,024
Columns: 7
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
encuesta_salud_train_gender_numeric %>% ggpairs(aes(color=genero), upper = list(continuous = wrap("cor", size = 3, hjust=0.8, alignPercent=0.15)), legend = 25) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle=45, vjust=0.5), legend.position = "bottom")

Realizamos el análisis solo numérico de la correlación entre variables

encuesta_salud_train %>% 
 correlate() %>% # convierte la matriz de corr en dataframe
  shave() %>% # solo muestra información debajo de la diagonal principal
  fashion() # acomoda los datos en forma tidy (por ej. redondeo de decimales)

Lo graficamos para tener las relaciones de una forma mas descriptiva

encuesta_salud_train %>% 
 correlate() %>% 
  network_plot(min_cor = 0.1)

Respecto a nuestra variable a predecir, el peso, observamos:

  • La variable altura es aquella que tiene mayor correlación con nuestra variable a predecir
  • Gráficamente también se puede observar la correlación entre altura y peso
  • Exceptuando la altura, el resto de las variables presentan una correlación muy baja
  • En una primera observación no parecieramos tener en el dataset la presencia de outliers que nos pueda traer complicaciones en análisis posteriores
  • A pesar que existen diferencias, no observamos distribuciones significativamente diferentes según el género

Análisis frecuencia de hambre mensual

Para las categorías de la variable frecuencia de hambre mensual, analice gráficamente la distribución en términos de frecuencia relativa de:

    1. El consumo semanal de verdura.
    1. El consumo semanal de comida grasa.

Agrupamos los valores para obtener la frecuencia tanto de consumo semanal de verduras como de consumo semanal de comida grasa

#encuesta_salud_train_numeric <- data.frame(encuesta_salud_train)       

consumo_verduras_freq = encuesta_salud_train %>%
  group_by(frecuencia_hambre_mensual, consumo_semanal_verdura) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))

consumo_verduras_freq %>% glimpse
Rows: 45
Columns: 4
Groups: frecuencia_hambre_mensual [6]
$ frecuencia_hambre_mensual <chr> "Algunas veces", "Algunas veces", "Algunas v…
$ consumo_semanal_verdura   <chr> "1 a 3 veces durante los últimos 7 días", "1…
$ n                         <int> 175, 92, 79, 21, 119, 27, 3, 73, 23, 14, 5, …
$ freq                      <dbl> 0.297113752, 0.156196944, 0.134125637, 0.035…
consumo_semanal_comida_grasa_freq = encuesta_salud_train %>%
  group_by(frecuencia_hambre_mensual, consumo_semanal_comida_grasa) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))

consumo_semanal_comida_grasa_freq %>% glimpse
Rows: 43
Columns: 4
Groups: frecuencia_hambre_mensual [6]
$ frecuencia_hambre_mensual    <chr> "Algunas veces", "Algunas veces", "Alguna…
$ consumo_semanal_comida_grasa <chr> "1 a 3 veces durante los últimos 7 días",…
$ n                            <int> 252, 62, 33, 18, 81, 24, 4, 115, 27, 6, 9…
$ freq                         <dbl> 0.427843803, 0.105263158, 0.056027165, 0.…

Frecuencia de hambre mensual / consumo semanal de verduras

ggplot(data = consumo_verduras_freq, aes(x = frecuencia_hambre_mensual, y = freq, fill = consumo_semanal_verdura)) + 
geom_bar(stat = "identity", position = position_dodge()) + 
xlab("Frecuencia de hambre mensual") + 
ylab("Consumo semanal de verduras") + 
theme(axis.text.x = element_text(angle = 90)) + 
coord_flip() + 
theme_bw() + 
labs(title = "Frecuencia de hambre mensual") +
scale_fill_brewer(palette = "Paired") + 
theme(axis.text.x = element_text(size = 10)) + 
theme(axis.title.x = element_text(size = 15)) + 
theme(axis.title.y = element_text(size = 15)) + 
theme(plot.title = element_text(size = 20))

Se puede observar como para las categorias Siempre y Casi siempre de hambre mensual, aumenta de forma significativa la cantidad de valores en consumo semanal verduras, que hacen referencia a no ingerir estos alimentos con recurrencia, y si aquellas que referencian a valores semanales (1 a 3 veces durante los ultimos 7 dias y 4 a 6 veces durante los ultimos siete dias) en lugar de valores diarios

Frecuencia de hambre mensual / consumo semanal de comida grasa

ggplot(data = consumo_semanal_comida_grasa_freq, aes(x = frecuencia_hambre_mensual, y = freq, fill = consumo_semanal_comida_grasa)) + 
geom_bar(stat = "identity", position = position_dodge()) + 
xlab("Frecuencia de hambre mensual") + 
ylab("Consumo de comida grasa") + 
theme(axis.text.x = element_text(angle = 90)) + 
coord_flip() + 
theme_bw() + 
labs(title = "Frecuencia de hambre mensual") +
scale_fill_brewer(palette = "Paired") + 
theme(axis.text.x = element_text(size = 10)) + 
theme(axis.title.x = element_text(size = 15)) + 
theme(axis.title.y = element_text(size = 15)) + 
theme(plot.title = element_text(size = 20))

Por otro lado, en cuanto al análisis de la relación entre el consumo de comida grasa y la frecuencia de hambre, observamos que el consumo habitual de comidad grasa, aumenta la frecuencia de hambre mensual. Buscando referencias a esta conclusión, existen estudios relacionados a la obecidad donde focalizan sobre esta relación donde los alimentos con altos contenidos grasos, dan más hambre

Modelo inicial

Creación del modelo

Se plantea que una primera alternativa para modelar el peso es: E(peso) = β0 +β1altura+β2edad+β3genero+β4diasActividadF isicaSemanal+β5consumoDiarioAlcohol

Realizamos una selección de las variables de nuestro interés para poder realizar el modelado, y observamos su estructura

#encuesta_salud_train %>% glimpse
encuesta_salud_modelo_base = encuesta_salud_train %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,dias_actividad_fisica_semanal,consumo_diario_alcohol)
encuesta_salud_modelo_base %>% glimpse
Rows: 7,024
Columns: 6
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …

Vemos cómo es la correlación entre las variables numéricas.

encuesta_salud_modelo_base %>% ggpairs(aes(color=genero), upper = list(continuous = wrap("cor", size = 3, hjust=0.8, alignPercent=0.15)), legend = 25) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle=45, vjust=0.5), legend.position = "bottom")

Armamos un modelo para predecir el peso en función de la altura, edad, género, días de actividad física semanal y consumo diario de alcohol.

E(peso) = β0 +β1altura+β2edad+β3genero+β4diasActividadFisicaSemanal+β5consumoDiarioAlcohol

# ajustamos modelo lineal múltiple
modelo_ln_base <- lm(peso ~ edad + genero + altura + dias_actividad_fisica_semanal + consumo_diario_alcohol, data = encuesta_salud_modelo_base)
# Resumen del modelo
tidy_ln_base <- tidy(modelo_ln_base, conf.int = TRUE)
tidy_ln_base

Evaluación de coeficientes

Significado de los coeficientes estimados

  • El valor de β0^ (ordenada al origen) es -68.92 kg, lo que corresponde al peso esperado de una persona femenina sin edad, sin altura, sin dias de actividad fisica y sin consumo diario en alcohol. Lo cual, este caso carecería de sentido ya que las personas femeninas deberían tener como valores de altura y edad numeros positivos > 0.

  • β0^ + βgeneroMasculino es la media del peso para las personas de género masculino, dada la edad, altura, dias de consumo de alcohol, dias de actividad fisica. Por lo tanto, βgeneroMasculino es la diferencia en los niveles medios de pesos de las personas masculinas respecto de las femeninas (categoría basal). Es decir, βgeneroMasculino (1.262643558) indica cuánto más alta es la función de respuesta (peso) para las personas masculinas respecto de las femeninas (categoría basal), dada la edad, altura, dias de consumo de alcohol y dias de actividad física.

  • El coeficiente estimado de edad es de 1.4kg, lo que indica que si mantenemos el número de altura, género, dias de actividad fisica y consumo diario de alchol, cada incremento adicional de edad corresponde a un aumento de 1.4kg, en promedio en el peso de la persona femenina. O lo que es igual, dadas dos personas con la misma altura, dias de actividad fisica, genero y consumo de alcohol, pero teniendo una un año más de edad que la otra, el peso esperado para la de mayor edad será 1.4kg más que la de menor pesaje.

  • El coeficiente estimado de altura es de 0.65kg, lo que indica que si mantenemos el número de género, edad, dias de actividad física y consumo diario de alchol, cada incremento adicional de altura corresponde a un aumento de 0.65kg, en promedio en el peso de la persona femenina. O lo que es igual, dadas dos personas con la misma edad, dias de actividad fisica, genero y consumo de alcohol, pero teniendo una un centímetro más de altura que la otra, el peso esperado para la de mayor altura será 0.65kg más que la de menor pesaje.

  • El coeficiente estimado de dias de actividad fisica es de -0.0874kg, lo que indica que si mantenemos el género, edad, altura y consumo diario de alchol, cada incremento de un día de actividad fisica adicional corresponde a un decremento de 0.0874kg, en promedio en el peso de la persona femenina. O lo que es igual, dadas dos personas con la misma edad, altura, género y consumo de alcohol, pero teniendo una un día de actividad fisica más que la otra, el peso esperado para la de mayor dias de actividad física será 0.0874kg menor que la de mayor pesaje.

  • El coeficiente estimado de dias de consumo de alcohol es de 0.00727kg, lo que indica que si mantenemos el número de género, edad, altura y días de actividad física, cada incremento de un día de consumo de alcohol adicional corresponde a un aumento de 0.00727kg, en promedio en el peso de la persona femenina. O lo que es igual, dadas dos personas con la misma edad, altura, género y días de actividad física, pero teniendo una un día de consumo de alcohol más que la otra, el peso esperado para la de mayor días de consumo de alcohol será 0.00727kg más que la de menor pesaje.

Significancia individual y global

Inferencia de los βk (test de significatividad individual)

Para evaluar la significatividad individual de cada una de las variables se analiza el test t que busca probar si el coeficiente de regresión correspondiente a dicha variable es distinto de 0 (figura en la tabla resumen de resultados de la regresión).

Es decir, buscamos probar:

H0:βk=0 H1:βk≠0.

options("scipen"=1)
tidy_ln_base %>% select(term, statistic, p.value, conf.low, conf.high)
significancia = tidy_ln_base %>% dplyr::select(term, statistic, p.value, conf.low, conf.high)
significancia

Evaluando la significancia de cada variable observamos lo siguiente

Para las variables edad, altura, generoMasculino, generoFemenino (basal):

  • Las variables resultan estadísticamente significativas para explicar el peso de las personas (p-valores < 0.05).
  • Además del resultado del test, podemos corroborar que los intervalos de confianza del 95% para los coeficientes estimados no contienen al 0 en ninguno de los casos.

Para las variables días de actividad física semanal y consumo diario de alcohol:

  • Las variables NO resultan estadísticamente significativas para explicar el peso de las personas (p-valores > 0.05).
  • Además del resultado del test, podemos corroborar que los intervalos de confianza del 95% para los coeficientes estimados contienen al 0 en los casos.
tidy(anova(modelo_ln_base))

La tabla de ANOVA muestra que, según el resultado del test F, la variable género en su conjunto, también resulta estadísticamente significativa para explicar al precio (p-valor < 0.05).

Test F (test de significatividad global)

Se construye para testear las hipótesis:

H0:β1=β2=···=βp−1=0

H1: no todos los βk (k=1,2,…,p−1) son iguales a 0.

Observemos que H0 dice que no hay vínculo entre la variable respuesta y las regresoras. En cambio, H1 dice que al menos una de las variables regresoras sirve para predecir a Y. Los resultados de este test se pueden observar haciendo un summary() del modelo o glance().

glance(modelo_ln_base)

F-statistic: 770.4 on 5 and 7018 DF, p-value: < 2.2e-16

Test significativo por lo que determinamos que se rechaza la hipotesis nula, nuestro modelo es relevante en comparación al modelo base, para determinar que arroja resultados significativos tomando la variable predictora

El R-cuadrado permite medir el porcentaje de variabilidad del fenómeno que el modelo logra explicar. Por este motivo es una métrica que nos permite evaluar la capacidad explicativa del modelo y poder comparar modelos entre sí bajo ciertas condiciones.

En nuestro caso tenemos un valor de Multiple R-squared: 0.3544, Adjusted R-squared: 0.3539

Modelo categóricos

Creación modelo categórico

Se sugiere probar un modelo que incopore el consumo semanal de snacks y una interacción entre el género y la edad, en lugar de actividad física y consumo de alcohol:

E(peso) = β0 + β1altura + β2edad + β3genero + β4consumoSemanalSnacks + β5genero · edad

encuesta_salud_train %>% glimpse
Rows: 7,024
Columns: 17
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, …
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4t…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca"…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 dí…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últ…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…
encuesta_salud_second_model = encuesta_salud_train %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,consumo_semanal_snacks)

encuesta_salud_second_model %>% glimpse
Rows: 7,024
Columns: 5
$ edad                   <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, 16, 14,…
$ genero                 <chr> "Femenino", "Masculino", "Masculino", "Masculin…
$ altura                 <int> 165, 178, 172, 170, 170, 178, 156, 163, 164, 16…
$ peso                   <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, 100, 33…
$ consumo_semanal_snacks <chr> "1 a 3 veces durante los últimos 7 días", "No c…

Dejamos al valor No comí comida salada o snacks en los últimos 7 días de consumo semanal de snacks como categoría basal en una nueva variable que llamaremos consumo_semanal_snacks_relevel

encuesta_salud_second_model$consumo_semanal_snacks_relevel <- relevel(as.factor(encuesta_salud_second_model$consumo_semanal_snacks), ref = 8)

Graficámos la variable categórica consumoSemanalSnacks para analizar sus valores en función del peso

# armo boxplots paralelos consumo de snacks en función del peso
ggplot( encuesta_salud_second_model, aes(x = fct_reorder(consumo_semanal_snacks_relevel, peso, .desc = T), y = peso)) + 
  geom_boxplot(alpha = 0.75, aes(fill = consumo_semanal_snacks_relevel)) + 
  theme_minimal() + 
  theme(legend.position = 'none')+
  labs(y = "Peso", x = "Consumo snacks")  +
  ggtitle("Boxplots del peso en función del consumo de snacks")+
  theme (axis.text.x = element_text(face="italic", colour="dark grey", size = 8, angle = 90))

modelo_ln_2_r <- lm(peso ~ consumo_semanal_snacks_relevel + edad + genero + altura + (edad * genero), data = encuesta_salud_second_model)
# Resumen del modelo
tidy_ln_2_r <- tidy(modelo_ln_2_r, conf.int = TRUE)
print(tidy_ln_2_r)
# A tibble: 12 × 7
   term                estimate std.error statistic   p.value conf.low conf.high
   <chr>                  <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
 1 (Intercept)          -64.2      2.83      -22.7  4.08e-110 -69.7      -58.7  
 2 consumo_semanal_sn…   -1.35     0.276      -4.89 1.03e-  6  -1.89      -0.810
 3 consumo_semanal_sn…   -0.608    0.456      -1.33 1.83e-  1  -1.50       0.286
 4 consumo_semanal_sn…   -1.09     0.685      -1.60 1.10e-  1  -2.44       0.247
 5 consumo_semanal_sn…   -1.28     1.01       -1.26 2.07e-  1  -3.26       0.707
 6 consumo_semanal_sn…   -2.27     0.450      -5.05 4.64e-  7  -3.15      -1.39 
 7 consumo_semanal_sn…   -2.57     0.881      -2.91 3.59e-  3  -4.29      -0.840
 8 consumo_semanal_sn…   -4.44     1.95       -2.28 2.29e-  2  -8.27      -0.615
 9 edad                   1.22     0.121      10.1  7.55e- 24   0.986      1.46 
10 generoMasculino       -4.61     2.68       -1.72 8.52e-  2  -9.86       0.640
11 altura                 0.643    0.0145     44.3  0           0.614      0.671
12 edad:generoMasculi…    0.391    0.179       2.19 2.88e-  2   0.0405     0.742

Interpretación de coeficientes / Análisis de significancia

Intercept

  • El valor de β0 (ordenada al origen) es -64.2 kg, lo que corresponde al peso esperado de una persona femenina, que no comió comida salada o snacks en los últimos 7 dias y contiene la suma del coeficiente edad:generoFemenino manteniendo el resto de las variables constantes. (Una variable de cada atributo categoróco forman parte del Intercept, en este caso género femenino, aquella que redefinimos para que sea basal de consumo de snacks No comió comida salada o snacks en los últimos 7 dias y la nueva variable creada edad:generoFemenino)

Coeficiente edad:generoMasculino:

  • El coeficiente estimado de edad:generoMasculino es de 0.391kg, lo que indica que si mantenemos el número de altura, genero, edad y consumo semanal de snacks, cada incremento de una unidad para edad:generoMasculino corresponde a un aumento de 0.391kg, en promedio en el peso de la persona femenina. => Significativa

Coeficiente del consumo semanal de snacks:

  • El coeficiente estimado de consumo semanal snacks 1 a 3 veces durante los últimos 7 días es de -1.35, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacks 1 a 3 veces durante los últimos 7 días, corresponde a una baja de 1.35kg, en promedio en el peso de la persona. => Significativa

  • El coeficiente estimado de consumo_semanal_snacks 1 vez al día es de -0.608, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacks 1 vez al día corresponde a una baja de 0.608, en promedio en el peso de la persona. No significativa

  • El coeficiente estimado de consumo_semanal_snacks 2 veces al día es de -1.09, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacks 2 veces al día corresponde a una baja de 1.09, en promedio en el peso de la persona. => No significativa

  • El coeficiente estimado de consumo_semanal_snacks 3 veces al día es de -1.28, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacks 3 veces al día corresponde a una baja de 1.28, en promedio en el peso de la persona. => No significativa

  • El coeficiente estimado de consumo_semanal_snacks 4 a 6 veces durante los últimos 7 días es de -2.27, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacks 4 a 6 veces durante los últimos 7 días corresponde a una baja de 2.27kg, en promedio en el peso de la persona. => Significativa

  • El coeficiente estimado de consumo_semanal_snacks 4 o más veces al día es de -2.57, si mantenemos el resto de las variables constantes, el incremento de una unidad para estimado de consumo_semanal_snacks 4 o más veces al día corresponde a una baja de 2.57kg, en promedio en el peso de la persona. => Significativa

  • El coeficiente estimado de consumo_semanal_snacks Dato perdido es de -4.44, si mantenemos el resto de las variables constantes, el incremento de una unidad para consumo_semanal_snacksDato perdido corresponde a una baja de 4.44kg, en promedio en el peso de la persona. => Significativa

Los coeficientes para las diferentes categorias de consumo semanal de snacks algunos son significativos y otros no.

Realizamos el test de anova para ver la significancia de las variables en su conjunto

tidy(anova(modelo_ln_2_r))

La tabla de ANOVA muestra que, según el resultado del test F, la variable consumo_semanal_snacks en su conjunto resulta estadísticamente significativa para explicar al precio (p-valor < 0.05).

Redefinicion de categóricas

Creamos una variable nueva que agrupe las tres variables categóricas que nos dieron una NO significancia en el análisis previo, las mismas serian

  • [3] “consumo_semanal_snacks_relevel1 vez al día”

  • [4] “consumo_semanal_snacks_relevel2 veces al día”

  • [5] “consumo_semanal_snacks_relevel3 veces al día”

encuesta_salud_train %>% glimpse
Rows: 7,024
Columns: 17
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, …
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4t…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca"…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 dí…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últ…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…
encuesta_salud_second_model_agrupado = encuesta_salud_train %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,consumo_semanal_snacks)

encuesta_salud_second_model_agrupado$consumo_semanal_snacks_relevel <- relevel(as.factor(encuesta_salud_second_model_agrupado$consumo_semanal_snacks), ref = 8)

Redefinimos la variable agrupando los valores categorizados como 1 vez al día, 2 veces al día y tres veces al día, en 1 a 3 veces al dia.

encuesta_salud_second_model_agrupado = encuesta_salud_second_model_agrupado %>% mutate(consumo_semanal_snacks = case_when(startsWith(consumo_semanal_snacks, "1 vez al día") ~ "1 a 3 veces al dia",
                                 startsWith(consumo_semanal_snacks, "2 veces al día") ~ "consumo_semanal_snacks 1 a 3 veces al dia",
                                 startsWith(consumo_semanal_snacks, "3 veces al día") ~ "consumo_semanal_snacks 1 a 3 veces al dia",
                                 TRUE ~ consumo_semanal_snacks))
#encuesta_salud_second_model_agrupado$consumo_semanal_snacks
modelo_ln_2_r_agrupado <- lm(peso ~ consumo_semanal_snacks + edad + genero + altura + (edad * genero), data = encuesta_salud_second_model_agrupado)
# Resumen del modelo
tidy_ln_2_r_agrupado <- tidy(modelo_ln_2_r_agrupado, conf.int = TRUE)
print(tidy_ln_2_r_agrupado)
# A tibble: 11 × 7
   term                estimate std.error statistic   p.value conf.low conf.high
   <chr>                  <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
 1 (Intercept)          -64.8      2.83     -22.9   4.14e-112 -70.4     -59.3   
 2 consumo_semanal_sn…   -0.744    0.440     -1.69  9.11e-  2  -1.61      0.119 
 3 consumo_semanal_sn…   -1.66     0.565     -2.94  3.28e-  3  -2.77     -0.554 
 4 consumo_semanal_sn…   -1.96     0.944     -2.07  3.81e-  2  -3.81     -0.108 
 5 consumo_semanal_sn…   -0.542    0.676     -0.801 4.23e-  1  -1.87      0.784 
 6 consumo_semanal_sn…   -3.83     1.98      -1.93  5.30e-  2  -7.72      0.0503
 7 consumo_semanal_sn…    0.608    0.456      1.33  1.83e-  1  -0.286     1.50  
 8 edad                   1.22     0.121     10.1   7.57e- 24   0.986     1.46  
 9 generoMasculino       -4.61     2.68      -1.72  8.52e-  2  -9.86      0.640 
10 altura                 0.643    0.0145    44.3   0           0.614     0.671 
11 edad:generoMasculi…    0.391    0.179      2.19  2.88e-  2   0.0405    0.742 

Observamos que la redefinición de las variables no significativas generando un agrupamiento para de las mismas mantiene la no significancia de estos valores para predecir el peso de una persona.

Evaluación de modelo

Ejecutamos las funcines tidy, glance y summary para obtener tanto la significacia de los atributos del modelo, la significancia del modelo en si (Test F) y la variabilidad del mismo (R-cuadrado y R-cuadrado ajustado).

options("scipen"=1)
tidy_ln_2_r %>%
  dplyr::select(term, statistic, p.value, conf.low, conf.high)
glance(modelo_ln_2_r)
summary(modelo_ln_2_r)

Call:
lm(formula = peso ~ consumo_semanal_snacks_relevel + edad + genero + 
    altura + (edad * genero), data = encuesta_salud_second_model)

Residuals:
    Min      1Q  Median      3Q     Max 
-29.005  -6.452  -1.157   5.052  75.767 

Coefficients:
                                                                      Estimate
(Intercept)                                                          -64.19940
consumo_semanal_snacks_relevel1 a 3 veces durante los últimos 7 días  -1.35163
consumo_semanal_snacks_relevel1 vez al día                            -0.60788
consumo_semanal_snacks_relevel2 veces al día                          -1.09466
consumo_semanal_snacks_relevel3 veces al día                          -1.27596
consumo_semanal_snacks_relevel4 a 6 veces durante los últimos 7 días  -2.27004
consumo_semanal_snacks_relevel4 o más veces al día                    -2.56697
consumo_semanal_snacks_relevelDato perdido                            -4.44042
edad                                                                   1.22309
generoMasculino                                                       -4.61150
altura                                                                 0.64292
edad:generoMasculino                                                   0.39146
                                                                     Std. Error
(Intercept)                                                             2.82849
consumo_semanal_snacks_relevel1 a 3 veces durante los últimos 7 días    0.27645
consumo_semanal_snacks_relevel1 vez al día                              0.45601
consumo_semanal_snacks_relevel2 veces al día                            0.68457
consumo_semanal_snacks_relevel3 veces al día                            1.01150
consumo_semanal_snacks_relevel4 a 6 veces durante los últimos 7 días    0.44992
consumo_semanal_snacks_relevel4 o más veces al día                      0.88112
consumo_semanal_snacks_relevelDato perdido                              1.95125
edad                                                                    0.12102
generoMasculino                                                         2.67877
altura                                                                  0.01453
edad:generoMasculino                                                    0.17902
                                                                     t value
(Intercept)                                                          -22.697
consumo_semanal_snacks_relevel1 a 3 veces durante los últimos 7 días  -4.889
consumo_semanal_snacks_relevel1 vez al día                            -1.333
consumo_semanal_snacks_relevel2 veces al día                          -1.599
consumo_semanal_snacks_relevel3 veces al día                          -1.261
consumo_semanal_snacks_relevel4 a 6 veces durante los últimos 7 días  -5.045
consumo_semanal_snacks_relevel4 o más veces al día                    -2.913
consumo_semanal_snacks_relevelDato perdido                            -2.276
edad                                                                  10.106
generoMasculino                                                       -1.722
altura                                                                44.250
edad:generoMasculino                                                   2.187
                                                                     Pr(>|t|)
(Intercept)                                                           < 2e-16
consumo_semanal_snacks_relevel1 a 3 veces durante los últimos 7 días 1.03e-06
consumo_semanal_snacks_relevel1 vez al día                            0.18257
consumo_semanal_snacks_relevel2 veces al día                          0.10985
consumo_semanal_snacks_relevel3 veces al día                          0.20719
consumo_semanal_snacks_relevel4 a 6 veces durante los últimos 7 días 4.64e-07
consumo_semanal_snacks_relevel4 o más veces al día                    0.00359
consumo_semanal_snacks_relevelDato perdido                            0.02289
edad                                                                  < 2e-16
generoMasculino                                                       0.08520
altura                                                                < 2e-16
edad:generoMasculino                                                  0.02880
                                                                        
(Intercept)                                                          ***
consumo_semanal_snacks_relevel1 a 3 veces durante los últimos 7 días ***
consumo_semanal_snacks_relevel1 vez al día                              
consumo_semanal_snacks_relevel2 veces al día                            
consumo_semanal_snacks_relevel3 veces al día                            
consumo_semanal_snacks_relevel4 a 6 veces durante los últimos 7 días ***
consumo_semanal_snacks_relevel4 o más veces al día                   ** 
consumo_semanal_snacks_relevelDato perdido                           *  
edad                                                                 ***
generoMasculino                                                      .  
altura                                                               ***
edad:generoMasculino                                                 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 9.887 on 7012 degrees of freedom
Multiple R-squared:  0.3585,    Adjusted R-squared:  0.3575 
F-statistic: 356.3 on 11 and 7012 DF,  p-value: < 2.2e-16

El porcentaje de variabilidad que explica este nuevo modelo es de R-cuadrado = 0.359, R-cuadrado ajustado = 0.358 Evaluando la significativida del modelo, obtenemos F-statistic: 356.3 on 11 and 7012 DF, p-value: < 2.2e-16 Modelo significativo

Modelos propios y evaluación

Creación de nuevos modelos

Proponemos un modelo que evalue el peso a partir del nivel educativo, consumo_semanal_comida_grasa, edad, genero, altura y una interrelación de altura, edad y género.

f(peso) ~ nivel_educativo + consumo_semanal_comida_grasa + edad + genero + altura + (altura * edad * genero)

Seleccionamos nuestras variables de interés

encuesta_salud_train %>% glimpse
Rows: 7,024
Columns: 17
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, …
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4t…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca"…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 dí…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últ…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…
encuesta_salud_third_model = encuesta_salud_train %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,nivel_educativo,consumo_semanal_comida_grasa)

encuesta_salud_third_model %>% glimpse
Rows: 7,024
Columns: 6
$ edad                         <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, 1…
$ genero                       <chr> "Femenino", "Masculino", "Masculino", "Ma…
$ altura                       <int> 165, 178, 172, 170, 170, 178, 156, 163, 1…
$ peso                         <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, 1…
$ nivel_educativo              <chr> "2do año/11vo grado nivel Polimodal o 4to…
$ consumo_semanal_comida_grasa <chr> "No comí comida alta en grasa en los últi…

Generamos el modelo lineal

modelo_ln_3_r <- lm(peso ~ nivel_educativo + consumo_semanal_comida_grasa + edad + genero + altura + (altura * edad * genero), data = encuesta_salud_third_model)
# Resumen del modelo
tidy_ln_3_r <- tidy(modelo_ln_3_r, conf.int = TRUE)
print(tidy_ln_3_r)
# A tibble: 20 × 7
   term                  estimate std.error statistic p.value conf.low conf.high
   <chr>                    <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>
 1 (Intercept)           -32.9      38.6      -0.852  0.394   -1.09e+2   42.8   
 2 nivel_educativo2do a…  -0.779     0.356    -2.19   0.0287  -1.48e+0   -0.0808
 3 nivel_educativo3er a…  -0.423     0.411    -1.03   0.304   -1.23e+0    0.383 
 4 nivel_educativo8vo g…  -0.930     0.458    -2.03   0.0424  -1.83e+0   -0.0321
 5 nivel_educativo9no g…  -0.400     0.379    -1.06   0.290   -1.14e+0    0.342 
 6 nivel_educativoDato …  -1.38      1.02     -1.35   0.176   -3.39e+0    0.622 
 7 consumo_semanal_comi…   1.10      0.428     2.57   0.0102   2.61e-1    1.94  
 8 consumo_semanal_comi…  -1.42      0.630    -2.26   0.0239  -2.66e+0   -0.188 
 9 consumo_semanal_comi…  -1.33      0.986    -1.35   0.177   -3.27e+0    0.601 
10 consumo_semanal_comi…  -0.915     0.349    -2.62   0.00870 -1.60e+0   -0.231 
11 consumo_semanal_comi…  -0.0676    0.849    -0.0796 0.937   -1.73e+0    1.60  
12 consumo_semanal_comi…  -2.94      1.40     -2.10   0.0354  -5.68e+0   -0.200 
13 consumo_semanal_comi…   0.0307    0.308     0.0996 0.921   -5.74e-1    0.635 
14 edad                   -0.343     2.57     -0.133  0.894   -5.38e+0    4.70  
15 generoMasculino        11.1      50.3       0.221  0.825   -8.75e+1  110.    
16 altura                  0.440     0.241     1.82   0.0682  -3.29e-2    0.913 
17 edad:altura             0.0101    0.0161    0.629  0.529   -2.14e-2    0.0416
18 generoMasculino:altu…  -0.0673    0.308    -0.219  0.827   -6.71e-1    0.536 
19 edad:generoMasculino   -1.74      3.38     -0.516  0.606   -8.37e+0    4.88  
20 edad:generoMasculino…   0.0111    0.0206    0.538  0.591   -2.93e-2    0.0515
glance(modelo_ln_3_r)
tidy(anova(modelo_ln_3_r))

Proponemos un modelo que evalue el peso a partir del nivel educativo, consumo_semanal_comida_grasa, genero, altura y una interrelación de dias consumo comida rápida y genero f(peso) ~ nivel_educativo + consumo_semanal_comida_grasa + genero + altura + (dias_consumo_comida_rapida * genero)

Seleccionamos nuestras variables de interés

encuesta_salud_train %>% glimpse
Rows: 7,024
Columns: 17
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, …
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4t…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca"…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 dí…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últ…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…
encuesta_salud_fourth_model = encuesta_salud_train %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,nivel_educativo,dias_consumo_comida_rapida)

encuesta_salud_fourth_model %>% glimpse
Rows: 7,024
Columns: 6
$ edad                       <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, 16,…
$ genero                     <chr> "Femenino", "Masculino", "Masculino", "Masc…
$ altura                     <int> 165, 178, 172, 170, 170, 178, 156, 163, 164…
$ peso                       <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, 100…
$ nivel_educativo            <chr> "2do año/11vo grado nivel Polimodal o 4to a…
$ dias_consumo_comida_rapida <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1, 3…

Generamos nuestro modelo lineal

modelo_ln_4_r <- lm(peso ~ nivel_educativo + dias_consumo_comida_rapida + genero + altura + (dias_consumo_comida_rapida * genero), data = encuesta_salud_fourth_model)
# Resumen del modelo
tidy_ln_4_r <- tidy(modelo_ln_4_r, conf.int = TRUE)
print(tidy_ln_4_r)
# A tibble: 10 × 7
   term                estimate std.error statistic   p.value conf.low conf.high
   <chr>                  <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
 1 (Intercept)         -5.12e+1    2.31    -22.2    3.15e-105  -55.7    -46.6   
 2 nivel_educativo2do…  1.86e-1    0.348     0.534  5.93e-  1   -0.496    0.867 
 3 nivel_educativo3er…  1.50e+0    0.372     4.03   5.52e-  5    0.771    2.23  
 4 nivel_educativo8vo… -2.76e+0    0.425    -6.48   1.00e- 10   -3.59    -1.92  
 5 nivel_educativo9no… -1.52e+0    0.370    -4.10   4.20e-  5   -2.24    -0.791 
 6 nivel_educativoDat… -8.26e-1    1.03     -0.801  4.23e-  1   -2.85     1.20  
 7 dias_consumo_comid… -5.82e-3    0.117    -0.0498 9.60e-  1   -0.235    0.223 
 8 generoMasculino      1.41e+0    0.306     4.62   3.86e-  6    0.815    2.01  
 9 altura               6.72e-1    0.0143   47.0    0            0.644    0.700 
10 dias_consumo_comid… -4.14e-1    0.175    -2.37   1.79e-  2   -0.757   -0.0712
glance(modelo_ln_4_r)
tidy(anova(modelo_ln_4_r))

Evaluación de modelos

Agrupamos todos los modelos que nos interesa comparar, en este caso es el modelo inicial, el modelo categóricas con las categorías redefinidas de la variable consumoSemanalSnacks y los dos modelos desarrollados en este punto, interrelación (altura * edad * genero) y (dias_consumo_comida_rapida * genero)

# armamos lista con todos los modelos
models <- list(modelo_ln_4_r = modelo_ln_4_r, modelo_ln_3_r = modelo_ln_3_r, modelo_ln_2_r = modelo_ln_2_r, modelo_ln_base = modelo_ln_base)
# calculamos las variables resumen
map_df(models, tidy, .id = "model")

Selección del mejor modelo en training

Utilizamos la función augment para predecir el peso sobre el dataset de training

lista_predicciones_training = map(.x = models, .f = augment) 

Calculamos el RMSE para todos los modelos a evaluar

map_dfr(.x = lista_predicciones_training, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Calculamos el MAE para todos los modelos a evaluar

map_dfr(.x = lista_predicciones_training, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Obtenemos el R-cuadrado y R-cuadrado ajustado para poder comparar la variabilidad de los modelos

df_evaluacion_train = map_df(models, glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train

Concluimos sobre el dataset de training

  • Analizando el R-cuadrado ajustado, el mejor modelo resulta ser modelo_ln_2_r que explica el peso en función de la altura, edad, género, dias de actividad física semanal y consumo diario de alcohol
  • Este modelo (modelo_ln_2_r) explica 35,8% de la variabilidad del peso, es decir, más que todos los restantes modelos.
  • El modelo con menor RMSE es el modelo_ln_3_r con un valor de 9.87, no coincide con el mejor R-cuadrado ajustado, pero si por una diferencia mínima modelo_ln_2_r (9.88)
  • En cuanto al MAE, tanto el modelo modelo_ln_2_r como modelo_ln_3_r, son los que tienen mejores resultados con 7.43

Evaluación en el dataset de testing

Utilizamos la función augment para predecir el peso sobre el dataset de training

# Aplicamos la función augment a los 4 modelos con el set de testing
encuesta_salud_test$consumo_semanal_snacks_relevel <- relevel(as.factor(encuesta_salud_test$consumo_semanal_snacks), ref = 8)
lista_predicciones_testing = map(.x = models, .f = augment, newdata = encuesta_salud_test) 

Calculamos el RMSE para todos los modelos a evaluar con las predicciones en training

# Obtenemos el RMSE para los 4 modelos
map_dfr(.x = lista_predicciones_testing, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Calculamos el MAE para todos los modelos a evaluar con las predicciones en training

map_dfr(.x = lista_predicciones_testing, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Concluimos sobre el dataset de testing:

  • Tanto el modelo modelo_ln_2_r como el modelo_ln_3_r son los que mejor RMSE tienen como en la medición sobre training con 10.2
  • Tanto el modelo modelo_ln_2_r como el modelo_ln_3_r son los que mejor MAE tienen como en la medición sobre training con 7.56

Diagnóstico del modelo

Realizaremos diferentes pruebas para validar el cumplimiento o no de los supuestos del modelo lineal, estos consisten de: εi∼N(0,σ2) independientes entre sí. Los errores son inobservables, por lo tanto tendremos que trabajar con su correlato empírico: los residuos en las técnicas de diagnóstico.

Procedemos a graficar el comportamiento de los residuos del modelo

au_modelos = map_df(models, augment, .id = "model")

g1 = ggplot(au_modelos %>% filter(model == "modelo_ln_base"), 
       aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0) +
  geom_smooth(se = FALSE) +
  labs(title = "Residuos vs valores predichos MODELO BASE") + 
  theme_bw()

g2 = ggplot(au_modelos %>% filter(model == "modelo_ln_base"), 
       aes(sample = .std.resid)) +
  stat_qq() +
  geom_abline() +
  labs(title = "Normal QQ plot MODELO BASE") + 
  theme_bw()

g3 = ggplot(au_modelos %>% filter(model == "modelo_ln_base"), 
       aes(.fitted, sqrt(abs(.std.resid)))) +
  geom_point() +
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Scale-location plot MODELO BASE")

g4 = ggplot(au_modelos %>% filter(model == "modelo_ln_base"), 
       aes(.hat, .std.resid)) +
  geom_vline(size = 2, colour = "white", xintercept = 0) +
  geom_hline(size = 2, colour = "white", yintercept = 0) +
  geom_point() + 
  geom_smooth(se = FALSE) + 
  theme_bw() +
  labs(title = "Residual vs leverage MODELO BASE")
# grafico todos juntos
grid.arrange(g1, g2, g3, g4, nrow = 2)

Conclusiones:

  • Residuos vs valores predichos: Parece no existir estructura en los datos, la varianza no parece incrementarse con los valores predichos, por lo que satisface el supuesto de homocedasticidad.
  • Normal QQ plot: Tanto el extremo superior derecho en particular, como el izquierdo inferior no se ajustan a la distribución teórica, no siguen una distribución normal
  • Residual vs leverage: Existen algunos puntos de alto leverage, habria que realizar un analisis mas profundo para entender la influencia de estos valores sobre el modelo, gráficamente no es simple determinarlo

Diagnóstico del modelo: El modelo creado no cumple con los supuestos del modelo lineal. Parecen existir problemas de falta de normalidad y presencia de observaciones de alto leverage.

Modelo robusto

Análisis del dataset

Leemos el nuevo archivo con la incorporación de algunas observaciones adicionales que pueden incluir valores atípicos

library(MASS)

encuesta_salud_train_original = read_csv("/Users/dfontenla/Maestria/2022C2/EEA/practica/repo/EEA-2022/TP1/encuesta_salud_modelo6.csv") %>% mutate(id = 1:nrow(.)) 
encuesta_salud_train_original %>% glimpse()
Rows: 7,129
Columns: 17
$ record                        <dbl> 48568, 39818, 11360, 50320, 26325, 44811…
$ edad                          <dbl> 17, 14, 16, 14, 15, 13, 16, 13, 15, 14, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ nivel_educativo               <chr> "3er año/12vo grado nivel Polimodal o 5t…
$ altura                        <dbl> 160, 189, 166, 178, 167, 164, 162, 170, …
$ peso                          <dbl> 51, 75, 50, 80, 50, 55, 60, 62, 48, 71, …
$ frecuencia_hambre_mensual     <chr> "Nunca", "Nunca", "Nunca", "Nunca", "Nun…
$ dias_consumo_comida_rapida    <dbl> 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0…
$ edad_consumo_alcohol          <chr> "12 o 13 años", "Nunca tomé alcohol más …
$ consumo_diario_alcohol        <dbl> 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, …
$ dias_actividad_fisica_semanal <dbl> 2, 0, 0, 2, 1, 3, 0, 2, 3, 7, 2, 2, 1, 6…
$ consumo_semanal_frutas        <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_gaseosas      <chr> "4 a 6 veces durante los últimos 7 días"…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días"…
$ consumo_semanal_comida_grasa  <chr> "4 a 6 veces durante los últimos 7 días"…
$ id                            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1…

Graficamos la relación peso / altura para observar las anomalias insertadas en el nuevo dataset

encuesta_salud_train_original %>% ggplot(., aes(x = altura, y = peso)) + 
  geom_point(alpha=0.5) + #capa de los datos
  theme_bw() +
  labs(title="Modelo Lineal Múltiple: Altura x Peso", x="Altura", y="Peso") 

Observamos que existen una cantidad de puntos relevantes que se alejan de forma categórica de la distribución general, una importante cantidad de outliers

Nos quedamos con las variables de interés para reproducir el modelo base

encuesta_salud_train_original_base = encuesta_salud_train_original %>%
                              # Seleccionamos las variables de interés
                              dplyr::select(edad, genero, altura, peso,dias_actividad_fisica_semanal,consumo_diario_alcohol)

encuesta_salud_train_original_base %>% glimpse
Rows: 7,129
Columns: 6
$ edad                          <dbl> 17, 14, 16, 14, 15, 13, 16, 13, 15, 14, …
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "M…
$ altura                        <dbl> 160, 189, 166, 178, 167, 164, 162, 170, …
$ peso                          <dbl> 51, 75, 50, 80, 50, 55, 60, 62, 48, 71, …
$ dias_actividad_fisica_semanal <dbl> 2, 0, 0, 2, 1, 3, 0, 2, 3, 7, 2, 2, 1, 6…
$ consumo_diario_alcohol        <dbl> 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, …

Graficamos las relaciones entre variables del nuevo dataset

encuesta_salud_train_original_base %>% ggpairs(aes(color=genero), upper = list(continuous = wrap("cor", size = 3, hjust=0.8, alignPercent=0.15)), legend = 25) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle=45, vjust=0.5), legend.position = "bottom")

Representamos la correlación de Pearson entre variables

encuesta_salud_train_original_base %>% 
 correlate() %>% # convierte la matriz de corr en dataframe
  shave() %>% # solo muestra información debajo de la diagonal principal
  fashion() # acomoda los datos en forma tidy (por ej. redondeo de decimales)

Podemos observar que la correlación entre peso y altura en relación al dataset base, disminuyó considerablemente pasando de un 0.5 a un 0.29, gráficamente se visualiza una clara correspondencia entre estos valores, ahora bien para el nuevo dataset existe una cantidad de datos outliers significativos

Creación del modelo

Ajustamos el dataset a nuestro modelo lineal múltiple base (peso ~ edad + genero + altura + dias_actividad_fisica_semanal + consumo_diario_alcohol)

# ajustamos modelo lineal múltiple
modelo_ds_original_ln_base <- lm(peso ~ edad + genero + altura + dias_actividad_fisica_semanal + consumo_diario_alcohol, data = encuesta_salud_train_original_base)
# Resumen del modelo
tidy_ln_ds_original_base <- tidy(modelo_ds_original_ln_base, conf.int = TRUE)
tidy_ln_ds_original_base

Realizamos un diagnóstico del modelo a través de los residuos

plot(modelo_ds_original_ln_base)

Vemos la significancia de los atributos

options("scipen"=1)
tidy_ln_ds_original_base %>%
  dplyr::select(term, statistic, p.value, conf.low, conf.high)

Analizamos la variabilidad explicativa del modelo

glance(modelo_ds_original_ln_base)

Observamos que la variabilidad porcentual explicada por R-cuadrado y R-cuadrado ajustado disminuye considerablemente en relación al mismo modelo entrenado en el dataset sin outliers

tidy(anova(modelo_ds_original_ln_base))

Evaluación comparativa del modelo base con el nuevo dataset y el original

Agrupamos los modelos para poder compararlos

# armamos lista con todos los modelos
models_leverage <- list(modelo_ds_original_ln_base = modelo_ds_original_ln_base, modelo_ln_base = modelo_ln_base)
# calculamos las variables resumen
map_df(models_leverage, tidy, .id = "model")
lista_predicciones_training_leverage = map(.x = models_leverage, .f = augment) 

Utilizamos la función glance sobre todos los modelos para comparar la variabilidad de los mismos

df_evaluacion_train_leverage = map_df(models_leverage, glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))
df_evaluacion_train_leverage

Calculamos el RMSE para todos los modelos a evaluar

map_dfr(.x = lista_predicciones_training_leverage, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Calculamos el MAE para todos los modelos a evaluar

map_dfr(.x = lista_predicciones_training_leverage, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Realizando la comparación de las metricas R-cuadrado ajustado, RMSE y MAE, observamos que:

  • En la evaluación del nuevo dataset con outliers, el R-cuadrado ajustado disminuyó significativamente por lo que nuestro modelo representa una variabilidad de los datos menor (modelo_ln_base:0.354 - modelo_ds_original_ln_base:0.109)
  • El error cuadrático medio aumenta de forma considerable (modelo_ln_base:9.91 - modelo_ds_original_ln_base:15.7)
  • El error absoluto MAE, también crece de forma cosiderable (modelo_ln_base:7.46 - modelo_ds_original_ln_base:9.23)

Concluimos con lo observado que nuestro modelo es sensible a outliers, y ante la precencia de estos, nuestras métricas de performance del modelo empeoran drásticamente

Creación modelo linear robusto

Ante la apreciación de como empeora nuestro modelo ante la existencia de outliers en nuestro dataset, procedemos a realizar un entrenamiento con un modelo lineal robusto el cual esperaremos que brinde mejores resultados debido a la ponderación que realizan sobre la influencia de los casos atípicos

# ajustamos modelo lineal múltiple
modelo_ds_original_ln_robust_base <- rlm(peso ~ edad + genero + altura + dias_actividad_fisica_semanal + consumo_diario_alcohol, data = encuesta_salud_train_original_base)
# # Resumen del modelo
tidy(modelo_ds_original_ln_base)
tidy(modelo_ds_original_ln_robust_base)

Comparando coeficientes, podemos ver como los relacionados al modelo robusto, no sufren grandes modificaciones en relación al modelo entrenado con el dataset sin outliers, mientras que en el modelo lineal común entrenado con el dataset con outliers, los coeficientes cambian considerablemente debido a estos datos.

options("scipen"=1)
tidy_ln_ds_original_robust <- tidy(modelo_ds_original_ln_robust_base, conf.int = TRUE)
tidy_ln_ds_original_robust %>% dplyr::select(term, statistic, conf.low, conf.high)

Evaluación comparativa del modelo común y robusto

models_robust <- list(modelo_ln_5_robust = modelo_ds_original_ln_robust_base, modelo_ln_5_base = modelo_ds_original_ln_base)
robust_predictions_over_training = map(.x = models_robust, .f = augment) 
map_dfr(.x = robust_predictions_over_training, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)
map_dfr(.x = robust_predictions_over_training, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)
  • Observamos que el RMSE no mejora con el modelo robusto (16.0)
  • Notamos una mejora importante en el error absoluto MAE, enter el modelo lineal (9.23) y robusto (8.76)

Evaluamos la performance de los modelos sobre el conjunto de testing

# Aplicamos la función augment a los 4 modelos con el set de testing
lista_predicciones_testing_robust = map(.x = models_robust, .f = augment, newdata = encuesta_salud_test) 
# Obtenemos el RMSE para los 4 modelos
map_dfr(.x = lista_predicciones_testing_robust, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)
map_dfr(.x = lista_predicciones_testing_robust, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% arrange(.estimate)

Evaluando los modelos sobre el conjunto de pruebas

  • El error cuadrático medio disminuye con el modelo robusto (10.3) en comparacion con el lineal (10.6) para el dataset con outliers, pero no de forma significativa, no es una métrica robusta, sensible a outliers
  • El error absoluto si disminuye con el modelo robusto (7.53) en comparación con el lineal (8.15) de forma significativa para el dataset con outliers, dando un valor similar a la medición del modelo sin la presencia de los valores atípicos

Conclusión, ante la presencia de una cantidad significativa de outliers, utilizar un modelo lineal robusto nos sirvio para poder tener un modelo que ante la presencia de estos valores atípicos se sigue comportando de una manera similar al modelo que fue entrenado sin outliers, mientras que el modelo lineal no robusto, siendo entrenado en un dataset con outliers, cambia significativamente sus coeficientes. Hace sentido poner foco sobre la métria MAE ya que es más robusto cuando los datos tienen outliers o datos atípicos y es la mejor opción a usar en esos casos

Consideraciones

Todos los fragmentos de códigos utilizados en el trabajo práctico fueron orientado a partir de notebooks propuestos por la cátedra En cuanto al modelo robusto donde utilice la librería MAAS, algunas fuentes de ejemplo fueron link1, link2

LS0tCnRpdGxlOiAiVFAgMTogUmVncmVzacOzbiBsaW5lYWwiCmF1dGhvcjogIkRhbWlhbiBGb250ZW5sYSIKZGF0ZTogIjE2IGRlIE9jdHVicmUgZGUgMjAyMiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKIyMgRGlhZ27Ds3N0aWNvIHkgRXZhbHVhY2nDs24gZGUgTW9kZWxvcyBkZSBSZWdyZXNpw7NuIExpbmVhbAoKIyMjIERhdGFzZXQKTG9zIGRhdG9zIGNvbiBsb3MgcXVlIHNlIHRyYWJhamFyw6EgZW4gZXN0ZSBUUCBwcm92aWVuZW4gZGUgbGEgM8KwIEVuY3Vlc3RhIE11bmRpYWwgZGUgU2FsdWQgRXNjb2xhciAoRU1TRSkgcHJvdmlzdG9zIHBvciBlbCAKTWluaXN0ZXJpbyBkZSBTYWx1ZCBbbGlua10oaHR0cDovL2RhdG9zLnNhbHVkLmdvYi5hci9kYXRhc2V0L2Jhc2UtZGUtZGF0b3MtZGUtbGEtMy1lbmN1ZXN0YS1tdW5kaWFsLWRlLXNhbHVkLWVzY29sYXItZW1zZS1jb24tcmVzdWx0YWRvcy1uYWNpb25hbGVzLWFyZ2VudGluYSkgZGUgbGEgUmVww7pibGljYSBBcmdlbnRpbmEuIEVzdGEgZW5jdWVzdGEgdHJhdGEgc29icmUgdGVtYXMgZGUgc2FsdWQgeSBow6FiaXRvcyBkZSBsYXMgcGVyc29uYXMgZW4gbGEgCmVzY3VlbGEgc2VjdW5kYXJpYSBxdWUgcHVlZGVuIGltcGFjdGFyIGVuIHN1IHNhbHVkLiAKCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CnJtKCBsaXN0PWxzKCkgKSAgI3JlbW92ZSBhbGwgb2JqZWN0cwpnYygpICAgICAgIApgYGAKCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9Cm9wdGlvbnMoc2NpcGVuPTk5OSkKYGBgCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiMgQ2FyZ2EgZGUgbGlicmVyw61hcwpsaWJyYXJ5KGNvcnJyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkocnNhbXBsZSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShHR2FsbHkpCgpgYGAKCkNvbWVuemFtb3MgbGV5ZW5kbyBsb3MgZGF0b3MgeSB2aWVuZG8gc3UgZXN0cnVjdHVyYSwgcGFyYSBlc3RvIHV0aWxpemFtb3MgbGEgZnVuY2nDs24gZ2xpbXBzZQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiNJbXBvcnRhbW9zIGxvcyBkYXRhc2V0cyBjb24gbG9zIHF1ZSB0cmFiYWphcmVtb3MKZW5jdWVzdGFfc2FsdWRfdHJhaW4gPSByZWFkLmNzdigiL1VzZXJzL2Rmb250ZW5sYS9NYWVzdHJpYS8yMDIyQzIvRUVBL3ByYWN0aWNhL3JlcG8vRUVBLTIwMjIvVFAxL2VuY3Vlc3RhX3NhbHVkX3RyYWluLmNzdiIsIGVuY29kaW5nID0gIlVURi04IikgJT4lIG11dGF0ZShpZCA9IDE6bnJvdyguKSkgCmVuY3Vlc3RhX3NhbHVkX3Rlc3QgPSByZWFkLmNzdigiL1VzZXJzL2Rmb250ZW5sYS9NYWVzdHJpYS8yMDIyQzIvRUVBL3ByYWN0aWNhL3JlcG8vRUVBLTIwMjIvVFAxL2VuY3Vlc3RhX3NhbHVkX3Rlc3QuY3N2IiwgZW5jb2RpbmcgPSAiVVRGLTgiKSAlPiUgbXV0YXRlKGlkID0gMTpucm93KC4pKSAKCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojZW5jdWVzdGFfc2FsdWRfdHJhaW4kbml2ZWxfZWR1Y2F0aXZvCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojT2JzZXJ2YW1vcyBsYSBlc3RydWN0dXJhIHkgdmFyaWFibGVzCmVuY3Vlc3RhX3NhbHVkX3RyYWluICU+JSBnbGltcHNlKCkKYGBgCgpFeGlzdGVuIDcwMjQgcmVnaXN0cm9zLCBjb21wdWVzdG8gZGUgMTcgZGF0b3MgZGlmZXJlbnRlcyBjb24gbG9zIHF1ZSB0cmFiYWphcmVtb3MgcGFyYSBlbnRyZW5hciBkaWZlcmVudGVzIG1vZGVsb3MgeSBwb2RlciByZWFsaXphciBsYSBwcmVkaWNjaW9uIApkZSBsYSB2YXJpYWJsZSBwZXNvLiAKTm9zIHF1ZWRhbW9zIGNvbiB0b2RvIGVsIGRhdGFzZXQsIGNvbW8gc2UgbWVuY2lvbmEgZW4gZWwgZW51bmNpYWRvLCBjb3JyZXNwb25kZW4gYSB1biByZWNvcnRlIChtdWVzdHJhKSBkZWwgZGF0YXNldCBvcmlnaW5hbCwgbHVlZ28gZGVsIHRyYXRhbWllbnRvIGRlIAp2YWxvcmVzIGF0w61waWNvcyBlIGluZ2VuaWVyw61hIGRlIGF0cmlidXRvcy4KCgo8IS0tIDEpIEFuw6FsaXNpcyBleHBsb3JhdG9yaW9zCkxlZXIgZWwgYXJjaGl2byDigJxlbmN1ZXN0YV9zYWx1ZF90cmFpbi5jc3bigJ0uIMK/UXXDqSBwdWVkZSBtZW5jaW9uYXIgc29icmUgc3UgZXN0cnVjdHVyYSB5IHZhcmlhYmxlcz8Kwr9Dw7NtbyBlcyBsYSBjb3JyZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzPyBVdGlsaWNlIHkgYW5hbGljZSBlbiBkZXRhbGxlIGFsZ8O6biBncsOhZmljbyBxdWUgc2lydmEKcGFyYSBzYWNhciBjb25jbHVzaW9uZXMgc29icmUgbGEgYXNvY2lhY2nDs24gZGUgdmFyaWFibGVzIHJlYWxpemFuZG8gYXBlcnR1cmEgcG9yIGfDqW5lcm8uIEVuIHBhcnRpY3VsYXIsIMK/Y8OzbW8KZXMgbGEgY29ycmVsYWNpw7NuIGVudHJlIGxhIHZhcmlhYmxlIGEgZXhwbGljYXIgKHBlc28pIHkgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzPwpQYXJhIGxhcyBjYXRlZ29yw61hcyBkZSBsYSB2YXJpYWJsZSBmcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsLCBhbmFsaWNlIGdyw6FmaWNhbWVudGUgbGEgZGlzdHJpYnVjacOzbiBlbgp0w6lybWlub3MgZGUgZnJlY3VlbmNpYSByZWxhdGl2YSBkZToKYSkgRWwgY29uc3VtbyBzZW1hbmFsIGRlIHZlcmR1cmEuCmIpIEVsIGNvbnN1bW8gc2VtYW5hbCBkZSBjb21pZGEgZ3Jhc2EuCsK/Q3XDoWxlcyBzb24gbGFzIHByaW5jaXBhbGVzIGNhcmFjdGVyw61zdGljYXMgcXVlIG9ic2VydmEgZW4gZXN0b3MgZ3LDoWZpY29zPyAtLT4KCiMjIEFuw6FsaXNpcyBleHBsb3JhdG9yaW9zCgojIyMgVmFsb3JlcyDDum5pY29zIHkgcG9yY2VudGFqZSBkZSBmYWx0YW50ZXMKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCnRhYmxhX2V4cGxvcmF0b3Jpb3MgPSAgZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2F0aGVyKC4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJ2YXJpYWJsZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9ICJ2YWxvcmVzIikgJT4lICMgYWdydXBhbW9zIHBvciBsYXMgdmFyaWFibGVzIGRlbCBzZXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieSh2YXJpYWJsZXMpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodmFsb3Jlc191bmljb3MgPSBuX2Rpc3RpbmN0KHZhbG9yZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvcmNlbnRhamVfZmFsdGFudGVzID0gc3VtKGlzLm5hKHZhbG9yZXMpKS9ucm93KGVuY3Vlc3RhX3NhbHVkX3RyYWluKSoxMDApICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MocG9yY2VudGFqZV9mYWx0YW50ZXMpLCB2YWxvcmVzX3VuaWNvcykgIyBvcmRlbmFtb3MgcG9yIHBvcmNlbnRhamUgZGUgZmFsdGFudGVzIHkgdmFsb3JlcyB1bmljb3MKdGFibGFfZXhwbG9yYXRvcmlvcwpgYGAKCk9ic2VydmFtb3MgdW4gZGF0YXNldCBxdWUgeWEgZXN0YSBjdXJhZG8gZGUgZGF0b3MgZmFsdGFudGVzIHkgc3UgY29tcG9zaWNpw7NuIGVzdGEgZGFkYSBwb3IgdW5hIG1heW9yIGNhbnRpZGFkIGRlIGRhdG9zIGNhdGVnw7NyaWNvcyBxdWUgY29udGludW9zCgojIyMgQW7DoWxpc2lzIGRlc2NyaXB0aXZvCgpSZWFsaXphbW9zIHVuIHByaW1lciBhbsOhbGlzaXMgZGVzY3JpcHRpdm8gZGUgbnVlc3RybyBkYXRhc2V0IGRlIGNvbHVtbmFzIHJlbGV2YW50ZXMuIFBhcmEgZWxsbyB1dGlsaXphbW9zIGxhIGZ1bmNpw7NuIGdncGFpcnMgc29icmUgbGFzIHZhcmlhYmxlcyAKbnVtw6lyaWNhcyBjb24gdW5hIGFwZXJ0dXJhIHBvciBsYSB2YXJpYWJsZSBkZSBzZXhvLgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiNPYnNlcnZhbW9zIGxhIGNvcnJlbGFjaW9uIGRlIHZhcmlhYmxlcwojZW5jdWVzdGFfc2FsdWRfdHJhaW5fbnVtZXJpYyA9IGVuY3Vlc3RhX3NhbHVkX3RyYWluICU+JSBkcGx5cjo6c2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKQoKIyBTZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMKZW5jdWVzdGFfc2FsdWRfdHJhaW5fZ2VuZGVyX251bWVyaWMgPSBlbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChlZGFkLCBnZW5lcm8sIGFsdHVyYSwgcGVzbyxkaWFzX2NvbnN1bW9fY29taWRhX3JhcGlkYSwgY29uc3Vtb19kaWFyaW9fYWxjb2hvbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsKQoKZW5jdWVzdGFfc2FsdWRfdHJhaW5fZ2VuZGVyX251bWVyaWMgJT4lIGdsaW1wc2UoKQoKZW5jdWVzdGFfc2FsdWRfdHJhaW5fZ2VuZGVyX251bWVyaWMgJT4lIGdncGFpcnMoYWVzKGNvbG9yPWdlbmVybyksIHVwcGVyID0gbGlzdChjb250aW51b3VzID0gd3JhcCgiY29yIiwgc2l6ZSA9IDMsIGhqdXN0PTAuOCwgYWxpZ25QZXJjZW50PTAuMTUpKSwgbGVnZW5kID0gMjUpICsgCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCgpSZWFsaXphbW9zIGVsIGFuw6FsaXNpcyBzb2xvIG51bcOpcmljbyBkZSBsYSBjb3JyZWxhY2nDs24gZW50cmUgdmFyaWFibGVzCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCmVuY3Vlc3RhX3NhbHVkX3RyYWluICU+JSAKIGNvcnJlbGF0ZSgpICU+JSAjIGNvbnZpZXJ0ZSBsYSBtYXRyaXogZGUgY29yciBlbiBkYXRhZnJhbWUKICBzaGF2ZSgpICU+JSAjIHNvbG8gbXVlc3RyYSBpbmZvcm1hY2nDs24gZGViYWpvIGRlIGxhIGRpYWdvbmFsIHByaW5jaXBhbAogIGZhc2hpb24oKSAjIGFjb21vZGEgbG9zIGRhdG9zIGVuIGZvcm1hIHRpZHkgKHBvciBlai4gcmVkb25kZW8gZGUgZGVjaW1hbGVzKQpgYGAKCkxvIGdyYWZpY2Ftb3MgcGFyYSB0ZW5lciBsYXMgcmVsYWNpb25lcyBkZSB1bmEgZm9ybWEgbWFzIGRlc2NyaXB0aXZhCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lIAogY29ycmVsYXRlKCkgJT4lIAogIG5ldHdvcmtfcGxvdChtaW5fY29yID0gMC4xKQpgYGAKClJlc3BlY3RvIGEgbnVlc3RyYSB2YXJpYWJsZSBhIHByZWRlY2lyLCAqKmVsIHBlc28qKiwgb2JzZXJ2YW1vczoKCi0gTGEgdmFyaWFibGUgYWx0dXJhIGVzIGFxdWVsbGEgcXVlIHRpZW5lIG1heW9yIGNvcnJlbGFjacOzbiBjb24gbnVlc3RyYSB2YXJpYWJsZSBhIHByZWRlY2lyCi0gR3LDoWZpY2FtZW50ZSB0YW1iacOpbiBzZSBwdWVkZSBvYnNlcnZhciBsYSBjb3JyZWxhY2nDs24gZW50cmUgYWx0dXJhIHkgcGVzbwotIEV4Y2VwdHVhbmRvIGxhIGFsdHVyYSwgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBwcmVzZW50YW4gdW5hIGNvcnJlbGFjacOzbiBtdXkgYmFqYSAKLSBFbiB1bmEgcHJpbWVyYSBvYnNlcnZhY2nDs24gbm8gcGFyZWNpZXJhbW9zIHRlbmVyIGVuIGVsIGRhdGFzZXQgbGEgcHJlc2VuY2lhIGRlIG91dGxpZXJzIHF1ZSBub3MgcHVlZGEgdHJhZXIgY29tcGxpY2FjaW9uZXMgZW4gYW7DoWxpc2lzIHBvc3RlcmlvcmVzCi0gQSBwZXNhciBxdWUgZXhpc3RlbiBkaWZlcmVuY2lhcywgbm8gb2JzZXJ2YW1vcyBkaXN0cmlidWNpb25lcyBzaWduaWZpY2F0aXZhbWVudGUgZGlmZXJlbnRlcyBzZWfDum4gZWwgZ8OpbmVybwoKCiMjIyBBbsOhbGlzaXMgZnJlY3VlbmNpYSBkZSBoYW1icmUgbWVuc3VhbAoKUGFyYSBsYXMgY2F0ZWdvcsOtYXMgZGUgbGEgdmFyaWFibGUgZnJlY3VlbmNpYSBkZSBoYW1icmUgbWVuc3VhbCwgYW5hbGljZSBncsOhZmljYW1lbnRlIGxhIGRpc3RyaWJ1Y2nDs24gZW4gdMOpcm1pbm9zIGRlIGZyZWN1ZW5jaWEgcmVsYXRpdmEgZGU6CgoqIGEpIEVsIGNvbnN1bW8gc2VtYW5hbCBkZSB2ZXJkdXJhLgoqIGIpIEVsIGNvbnN1bW8gc2VtYW5hbCBkZSBjb21pZGEgZ3Jhc2EuCgoKQWdydXBhbW9zIGxvcyB2YWxvcmVzIHBhcmEgb2J0ZW5lciBsYSBmcmVjdWVuY2lhIHRhbnRvIGRlIGNvbnN1bW8gc2VtYW5hbCBkZSB2ZXJkdXJhcyBjb21vIGRlIGNvbnN1bW8gc2VtYW5hbCBkZSBjb21pZGEgZ3Jhc2EKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQoKI2VuY3Vlc3RhX3NhbHVkX3RyYWluX251bWVyaWMgPC0gZGF0YS5mcmFtZShlbmN1ZXN0YV9zYWx1ZF90cmFpbikgICAgICAgCgpjb25zdW1vX3ZlcmR1cmFzX2ZyZXEgPSBlbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUKICBncm91cF9ieShmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsLCBjb25zdW1vX3NlbWFuYWxfdmVyZHVyYSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JQogIG11dGF0ZShmcmVxID0gbiAvIHN1bShuKSkKCmNvbnN1bW9fdmVyZHVyYXNfZnJlcSAlPiUgZ2xpbXBzZQoKY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYV9mcmVxID0gZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lCiAgZ3JvdXBfYnkoZnJlY3VlbmNpYV9oYW1icmVfbWVuc3VhbCwgY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JQogIG11dGF0ZShmcmVxID0gbiAvIHN1bShuKSkKCmNvbnN1bW9fc2VtYW5hbF9jb21pZGFfZ3Jhc2FfZnJlcSAlPiUgZ2xpbXBzZQoKYGBgCgojIyMjIEZyZWN1ZW5jaWEgZGUgaGFtYnJlIG1lbnN1YWwgLyBjb25zdW1vIHNlbWFuYWwgZGUgdmVyZHVyYXMKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZ2dwbG90KGRhdGEgPSBjb25zdW1vX3ZlcmR1cmFzX2ZyZXEsIGFlcyh4ID0gZnJlY3VlbmNpYV9oYW1icmVfbWVuc3VhbCwgeSA9IGZyZXEsIGZpbGwgPSBjb25zdW1vX3NlbWFuYWxfdmVyZHVyYSkpICsgCmdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpICsgCnhsYWIoIkZyZWN1ZW5jaWEgZGUgaGFtYnJlIG1lbnN1YWwiKSArIAp5bGFiKCJDb25zdW1vIHNlbWFuYWwgZGUgdmVyZHVyYXMiKSArIAp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgKyAKY29vcmRfZmxpcCgpICsgCnRoZW1lX2J3KCkgKyAKbGFicyh0aXRsZSA9ICJGcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsIikgKwpzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpICsgCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsgCnRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpKSArIAp0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKyAKdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApKQoKYGBgCgpTZSBwdWVkZSBvYnNlcnZhciBjb21vIHBhcmEgbGFzIGNhdGVnb3JpYXMgKlNpZW1wcmUqIHkgKkNhc2kgc2llbXByZSogZGUgaGFtYnJlIG1lbnN1YWwsIGF1bWVudGEgZGUgZm9ybWEgc2lnbmlmaWNhdGl2YSBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIGVuICpjb25zdW1vIHNlbWFuYWwgdmVyZHVyYXMqLCBxdWUgaGFjZW4gcmVmZXJlbmNpYSBhIG5vIAppbmdlcmlyIGVzdG9zIGFsaW1lbnRvcyBjb24gcmVjdXJyZW5jaWEsIHkgc2kgYXF1ZWxsYXMgcXVlIHJlZmVyZW5jaWFuIGEgdmFsb3JlcyBzZW1hbmFsZXMgKCoxIGEgMyB2ZWNlcyBkdXJhbnRlIGxvcyB1bHRpbW9zIDcgZGlhcyogeSAqNCBhIDYgdmVjZXMgZHVyYW50ZSBsb3MgdWx0aW1vcyBzaWV0ZSBkaWFzKikgCmVuIGx1Z2FyIGRlIHZhbG9yZXMgZGlhcmlvcwoKIyMjIyBGcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsIC8gY29uc3VtbyBzZW1hbmFsIGRlIGNvbWlkYSBncmFzYQoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpnZ3Bsb3QoZGF0YSA9IGNvbnN1bW9fc2VtYW5hbF9jb21pZGFfZ3Jhc2FfZnJlcSwgYWVzKHggPSBmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsLCB5ID0gZnJlcSwgZmlsbCA9IGNvbnN1bW9fc2VtYW5hbF9jb21pZGFfZ3Jhc2EpKSArIApnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSArIAp4bGFiKCJGcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsIikgKyAKeWxhYigiQ29uc3VtbyBkZSBjb21pZGEgZ3Jhc2EiKSArIAp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgKyAKY29vcmRfZmxpcCgpICsgCnRoZW1lX2J3KCkgKyAKbGFicyh0aXRsZSA9ICJGcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsIikgKwpzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpICsgCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsgCnRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpKSArIAp0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKyAKdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApKQoKYGBgCgpQb3Igb3RybyBsYWRvLCBlbiBjdWFudG8gYWwgYW7DoWxpc2lzIGRlIGxhIHJlbGFjacOzbiBlbnRyZSBlbCBjb25zdW1vIGRlIGNvbWlkYSBncmFzYSB5IGxhIGZyZWN1ZW5jaWEgZGUgaGFtYnJlLCBvYnNlcnZhbW9zIHF1ZSBlbCBjb25zdW1vIGhhYml0dWFsIGRlIGNvbWlkYWQgZ3Jhc2EsIAphdW1lbnRhIGxhIGZyZWN1ZW5jaWEgZGUgaGFtYnJlIG1lbnN1YWwuIEJ1c2NhbmRvIHJlZmVyZW5jaWFzIGEgZXN0YSBjb25jbHVzacOzbiwgZXhpc3RlbiBlc3R1ZGlvcyByZWxhY2lvbmFkb3MgYSBsYSBvYmVjaWRhZCBkb25kZSBmb2NhbGl6YW4gc29icmUgZXN0YSByZWxhY2nDs24gZG9uZGUKbG9zIGFsaW1lbnRvcyBjb24gYWx0b3MgY29udGVuaWRvcyBncmFzb3MsIGRhbiBtw6FzIGhhbWJyZQoKPCEtLSAyKSBNb2RlbG8gaW5pY2lhbApTZSBwbGFudGVhIHF1ZSB1bmEgcHJpbWVyYSBhbHRlcm5hdGl2YSBwYXJhIG1vZGVsYXIgZWwgcGVzbyBlczoKRShwZXNvKSA9IM6yMCArzrIxYWx0dXJhK86yMmVkYWQrzrIzZ2VuZXJvK86yNGRpYXNBY3RpdmlkYWRGIGlzaWNhU2VtYW5hbCvOsjVjb25zdW1vRGlhcmlvQWxjb2hvbArCv0N1w6FsIGVzIGxhIGludGVycHJldGFjacOzbiBkZSBjYWRhIHVubyBkZSBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcz8gwr9Tb24gc2lnbmlmaWNhdGl2b3M/IMK/RWwgbW9kZWxvIHJlc3VsdGEKc2lnbmlmaWNhdGl2byBwYXJhIGV4cGxpY2FyIGVsIHBlc28/IMK/UXXDqSBwb3JjZW50YWplIGRlIGxhIHZhcmlhYmlsaWRhZCBleHBsaWNhIGVsIG1vZGVsbz8gLS0+CgojIyBNb2RlbG8gaW5pY2lhbAoKIyMjIENyZWFjacOzbiBkZWwgbW9kZWxvClNlIHBsYW50ZWEgcXVlIHVuYSBwcmltZXJhIGFsdGVybmF0aXZhIHBhcmEgbW9kZWxhciBlbCBwZXNvIGVzOiAKKkUocGVzbykgPSDOsjAgK86yMWFsdHVyYSvOsjJlZGFkK86yM2dlbmVybyvOsjRkaWFzQWN0aXZpZGFkRiBpc2ljYVNlbWFuYWwrzrI1Y29uc3Vtb0RpYXJpb0FsY29ob2wqCgpSZWFsaXphbW9zIHVuYSBzZWxlY2Npw7NuIGRlIGxhcyB2YXJpYWJsZXMgZGUgbnVlc3RybyBpbnRlcsOpcyBwYXJhIHBvZGVyIHJlYWxpemFyIGVsIG1vZGVsYWRvLCB5IG9ic2VydmFtb3Mgc3UgZXN0cnVjdHVyYQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CgojZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lIGdsaW1wc2UKZW5jdWVzdGFfc2FsdWRfbW9kZWxvX2Jhc2UgPSBlbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChlZGFkLCBnZW5lcm8sIGFsdHVyYSwgcGVzbyxkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCxjb25zdW1vX2RpYXJpb19hbGNvaG9sKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZW5jdWVzdGFfc2FsdWRfbW9kZWxvX2Jhc2UgJT4lIGdsaW1wc2UKYGBgCgpWZW1vcyBjw7NtbyBlcyBsYSBjb3JyZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzLgoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQoKZW5jdWVzdGFfc2FsdWRfbW9kZWxvX2Jhc2UgJT4lIGdncGFpcnMoYWVzKGNvbG9yPWdlbmVybyksIHVwcGVyID0gbGlzdChjb250aW51b3VzID0gd3JhcCgiY29yIiwgc2l6ZSA9IDMsIGhqdXN0PTAuOCwgYWxpZ25QZXJjZW50PTAuMTUpKSwgbGVnZW5kID0gMjUpICsgCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKYGBgCgoKQXJtYW1vcyB1biBtb2RlbG8gcGFyYSBwcmVkZWNpciBlbCAqcGVzbyogZW4gZnVuY2nDs24gZGUgbGEgKmFsdHVyYSwgZWRhZCwgZ8OpbmVybywgZMOtYXMgZGUgYWN0aXZpZGFkIGbDrXNpY2Egc2VtYW5hbCB5IGNvbnN1bW8gZGlhcmlvIGRlIGFsY29ob2wqLiAKCipFKHBlc28pID0gzrIwICvOsjFhbHR1cmErzrIyZWRhZCvOsjNnZW5lcm8rzrI0ZGlhc0FjdGl2aWRhZEZpc2ljYVNlbWFuYWwrzrI1Y29uc3Vtb0RpYXJpb0FsY29ob2wqCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiMgYWp1c3RhbW9zIG1vZGVsbyBsaW5lYWwgbcO6bHRpcGxlCm1vZGVsb19sbl9iYXNlIDwtIGxtKHBlc28gfiBlZGFkICsgZ2VuZXJvICsgYWx0dXJhICsgZGlhc19hY3RpdmlkYWRfZmlzaWNhX3NlbWFuYWwgKyBjb25zdW1vX2RpYXJpb19hbGNvaG9sLCBkYXRhID0gZW5jdWVzdGFfc2FsdWRfbW9kZWxvX2Jhc2UpCiMgUmVzdW1lbiBkZWwgbW9kZWxvCnRpZHlfbG5fYmFzZSA8LSB0aWR5KG1vZGVsb19sbl9iYXNlLCBjb25mLmludCA9IFRSVUUpCnRpZHlfbG5fYmFzZQpgYGAKCiMjIyBFdmFsdWFjacOzbiBkZSBjb2VmaWNpZW50ZXMgCgojIyMjIFNpZ25pZmljYWRvIGRlIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zCgotIEVsIHZhbG9yIGRlIM6yMF4gKG9yZGVuYWRhIGFsIG9yaWdlbikgZXMgLTY4LjkyIGtnLCBsbyBxdWUgY29ycmVzcG9uZGUgYWwgcGVzbyBlc3BlcmFkbyBkZSB1bmEgcGVyc29uYSBmZW1lbmluYSBzaW4gZWRhZCwgc2luIGFsdHVyYSwgc2luIGRpYXMgZGUgYWN0aXZpZGFkIGZpc2ljYSB5IApzaW4gY29uc3VtbyBkaWFyaW8gZW4gYWxjb2hvbC4gTG8gY3VhbCwgZXN0ZSBjYXNvIGNhcmVjZXLDrWEgZGUgc2VudGlkbyB5YSBxdWUgbGFzIHBlcnNvbmFzIGZlbWVuaW5hcyBkZWJlcsOtYW4gdGVuZXIgY29tbyB2YWxvcmVzIGRlIGFsdHVyYSB5IGVkYWQgbnVtZXJvcyBwb3NpdGl2b3MgPiAwLgoKLSDOsjBeICsgzrJnZW5lcm9NYXNjdWxpbm8gZXMgbGEgbWVkaWEgZGVsIHBlc28gcGFyYSBsYXMgcGVyc29uYXMgZGUgZ8OpbmVybyBtYXNjdWxpbm8sIGRhZGEgbGEgZWRhZCwgYWx0dXJhLCBkaWFzIGRlIGNvbnN1bW8gZGUgYWxjb2hvbCwgZGlhcyBkZSBhY3RpdmlkYWQgZmlzaWNhLiAKUG9yIGxvIHRhbnRvLCDOsmdlbmVyb01hc2N1bGlubyBlcyBsYSBkaWZlcmVuY2lhIGVuIGxvcyBuaXZlbGVzIG1lZGlvcyBkZSBwZXNvcyBkZSBsYXMgcGVyc29uYXMgbWFzY3VsaW5hcyByZXNwZWN0byBkZSBsYXMgZmVtZW5pbmFzIChjYXRlZ29yw61hIGJhc2FsKS4KRXMgZGVjaXIsIM6yZ2VuZXJvTWFzY3VsaW5vICgxLjI2MjY0MzU1OCkgaW5kaWNhIGN1w6FudG8gbcOhcyBhbHRhIGVzIGxhIGZ1bmNpw7NuIGRlIHJlc3B1ZXN0YSAocGVzbykgcGFyYSBsYXMgcGVyc29uYXMgbWFzY3VsaW5hcyByZXNwZWN0byBkZSBsYXMgZmVtZW5pbmFzIChjYXRlZ29yw61hIGJhc2FsKSwgCmRhZGEgbGEgZWRhZCwgYWx0dXJhLCBkaWFzIGRlIGNvbnN1bW8gZGUgYWxjb2hvbCB5IGRpYXMgZGUgYWN0aXZpZGFkIGbDrXNpY2EuCgotIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIGVkYWQgZXMgZGUgMS40a2csIGxvIHF1ZSBpbmRpY2EgcXVlIHNpIG1hbnRlbmVtb3MgZWwgbsO6bWVybyBkZSBhbHR1cmEsIGfDqW5lcm8sIGRpYXMgZGUgYWN0aXZpZGFkIGZpc2ljYSB5IGNvbnN1bW8gZGlhcmlvIGRlIGFsY2hvbCwgCmNhZGEgaW5jcmVtZW50byBhZGljaW9uYWwgZGUgZWRhZCBjb3JyZXNwb25kZSBhIHVuIGF1bWVudG8gZGUgMS40a2csIGVuIHByb21lZGlvIGVuIGVsIHBlc28gZGUgbGEgcGVyc29uYSBmZW1lbmluYS4gTyBsbyBxdWUgZXMgaWd1YWwsIGRhZGFzIGRvcyBwZXJzb25hcyBjb24gbGEgbWlzbWEgYWx0dXJhLCAKZGlhcyBkZSBhY3RpdmlkYWQgZmlzaWNhLCBnZW5lcm8geSBjb25zdW1vIGRlIGFsY29ob2wsIHBlcm8gdGVuaWVuZG8gdW5hIHVuIGHDsW8gbcOhcyBkZSBlZGFkIHF1ZSBsYSBvdHJhLCBlbCBwZXNvIGVzcGVyYWRvIHBhcmEgbGEgZGUgbWF5b3IgZWRhZCBzZXLDoSAxLjRrZyBtw6FzIHF1ZSBsYSBkZSBtZW5vciBwZXNhamUuCgotIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIGFsdHVyYSBlcyBkZSAwLjY1a2csIGxvIHF1ZSBpbmRpY2EgcXVlIHNpIG1hbnRlbmVtb3MgZWwgbsO6bWVybyBkZSBnw6luZXJvLCBlZGFkLCBkaWFzIGRlIGFjdGl2aWRhZCBmw61zaWNhIHkgY29uc3VtbyBkaWFyaW8gZGUgYWxjaG9sLCAKY2FkYSBpbmNyZW1lbnRvIGFkaWNpb25hbCBkZSBhbHR1cmEgY29ycmVzcG9uZGUgYSB1biBhdW1lbnRvIGRlIDAuNjVrZywgZW4gcHJvbWVkaW8gZW4gZWwgcGVzbyBkZSBsYSBwZXJzb25hIGZlbWVuaW5hLiBPIGxvIHF1ZSBlcyBpZ3VhbCwgZGFkYXMgZG9zIHBlcnNvbmFzIGNvbiBsYSAKbWlzbWEgZWRhZCwgZGlhcyBkZSBhY3RpdmlkYWQgZmlzaWNhLCBnZW5lcm8geSBjb25zdW1vIGRlIGFsY29ob2wsIHBlcm8gdGVuaWVuZG8gdW5hIHVuIGNlbnTDrW1ldHJvIG3DoXMgZGUgYWx0dXJhIHF1ZSBsYSBvdHJhLCBlbCBwZXNvIGVzcGVyYWRvIHBhcmEgbGEgZGUgbWF5b3IgYWx0dXJhIHNlcsOhIDAuNjVrZyBtw6FzIHF1ZSBsYSBkZSBtZW5vciBwZXNhamUuCgotIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIGRpYXMgZGUgYWN0aXZpZGFkIGZpc2ljYSBlcyBkZSAtMC4wODc0a2csIGxvIHF1ZSBpbmRpY2EgcXVlIHNpIG1hbnRlbmVtb3MgZWwgZ8OpbmVybywgZWRhZCwgYWx0dXJhIHkgY29uc3VtbyBkaWFyaW8gZGUgYWxjaG9sLCAKY2FkYSBpbmNyZW1lbnRvIGRlIHVuIGTDrWEgZGUgYWN0aXZpZGFkIGZpc2ljYSBhZGljaW9uYWwgY29ycmVzcG9uZGUgYSB1biBkZWNyZW1lbnRvIGRlIDAuMDg3NGtnLCBlbiBwcm9tZWRpbyBlbiBlbCBwZXNvIGRlIGxhIHBlcnNvbmEgZmVtZW5pbmEuIE8gbG8gcXVlIGVzIGlndWFsLCBkYWRhcyBkb3MgcGVyc29uYXMgY29uIGxhIAptaXNtYSBlZGFkLCBhbHR1cmEsIGfDqW5lcm8geSBjb25zdW1vIGRlIGFsY29ob2wsIHBlcm8gdGVuaWVuZG8gdW5hIHVuIGTDrWEgZGUgYWN0aXZpZGFkIGZpc2ljYSBtw6FzIHF1ZSBsYSBvdHJhLCBlbCBwZXNvIGVzcGVyYWRvIHBhcmEgbGEgZGUgbWF5b3IgZGlhcyBkZSBhY3RpdmlkYWQgZsOtc2ljYSBzZXLDoSAwLjA4NzRrZyBtZW5vciBxdWUgbGEgZGUgbWF5b3IgcGVzYWplLgoKLSBFbCBjb2VmaWNpZW50ZSBlc3RpbWFkbyBkZSBkaWFzIGRlIGNvbnN1bW8gZGUgYWxjb2hvbCBlcyBkZSAwLjAwNzI3a2csIGxvIHF1ZSBpbmRpY2EgcXVlIHNpIG1hbnRlbmVtb3MgZWwgbsO6bWVybyBkZSBnw6luZXJvLCBlZGFkLCBhbHR1cmEgeSBkw61hcyBkZSBhY3RpdmlkYWQgZsOtc2ljYSwgCmNhZGEgaW5jcmVtZW50byBkZSB1biBkw61hIGRlIGNvbnN1bW8gZGUgYWxjb2hvbCBhZGljaW9uYWwgY29ycmVzcG9uZGUgYSB1biBhdW1lbnRvIGRlIDAuMDA3MjdrZywgZW4gcHJvbWVkaW8gZW4gZWwgcGVzbyBkZSBsYSBwZXJzb25hIGZlbWVuaW5hLiBPIGxvIHF1ZSBlcyBpZ3VhbCwgZGFkYXMgZG9zIHBlcnNvbmFzIGNvbiBsYSAKbWlzbWEgZWRhZCwgYWx0dXJhLCBnw6luZXJvIHkgZMOtYXMgZGUgYWN0aXZpZGFkIGbDrXNpY2EsIHBlcm8gdGVuaWVuZG8gdW5hIHVuIGTDrWEgZGUgY29uc3VtbyBkZSBhbGNvaG9sIG3DoXMgcXVlIGxhIG90cmEsIGVsIHBlc28gZXNwZXJhZG8gcGFyYSBsYSBkZSBtYXlvciBkw61hcyBkZSBjb25zdW1vIGRlIGFsY29ob2wgc2Vyw6EgMC4wMDcyN2tnIG3DoXMgcXVlIGxhIGRlIG1lbm9yIHBlc2FqZS4KCiMjIyBTaWduaWZpY2FuY2lhIGluZGl2aWR1YWwgeSBnbG9iYWwKCiMjIyMgSW5mZXJlbmNpYSBkZSBsb3MgzrJrICh0ZXN0IGRlIHNpZ25pZmljYXRpdmlkYWQgaW5kaXZpZHVhbCkKClBhcmEgZXZhbHVhciBsYSBzaWduaWZpY2F0aXZpZGFkIGluZGl2aWR1YWwgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBzZSBhbmFsaXphIGVsIHRlc3QgdCBxdWUgYnVzY2EgcHJvYmFyIHNpIGVsIGNvZWZpY2llbnRlIGRlIHJlZ3Jlc2nDs24gY29ycmVzcG9uZGllbnRlIGEgZGljaGEgdmFyaWFibGUgZXMgZGlzdGludG8gZGUgMCAoZmlndXJhIGVuIGxhIHRhYmxhIHJlc3VtZW4gZGUgcmVzdWx0YWRvcyBkZSBsYSByZWdyZXNpw7NuKS4KCkVzIGRlY2lyLCBidXNjYW1vcyBwcm9iYXI6CgoqSDA6zrJrPTAqCipIMTrOsmviiaAwLioKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0Kb3B0aW9ucygic2NpcGVuIj0xKQp0aWR5X2xuX2Jhc2UgJT4lIHNlbGVjdCh0ZXJtLCBzdGF0aXN0aWMsIHAudmFsdWUsIGNvbmYubG93LCBjb25mLmhpZ2gpCnNpZ25pZmljYW5jaWEgPSB0aWR5X2xuX2Jhc2UgJT4lIGRwbHlyOjpzZWxlY3QodGVybSwgc3RhdGlzdGljLCBwLnZhbHVlLCBjb25mLmxvdywgY29uZi5oaWdoKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0Kc2lnbmlmaWNhbmNpYQpgYGAKCkV2YWx1YW5kbyBsYSBzaWduaWZpY2FuY2lhIGRlIGNhZGEgdmFyaWFibGUgb2JzZXJ2YW1vcyBsbyBzaWd1aWVudGUKClBhcmEgbGFzIHZhcmlhYmxlcyBlZGFkLCBhbHR1cmEsIGdlbmVyb01hc2N1bGlubywgZ2VuZXJvRmVtZW5pbm8gKGJhc2FsKToKCi0gTGFzIHZhcmlhYmxlcyByZXN1bHRhbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyBwYXJhIGV4cGxpY2FyIGVsIHBlc28gZGUgbGFzIHBlcnNvbmFzIChwLXZhbG9yZXMgPCAwLjA1KS4KLSBBZGVtw6FzIGRlbCByZXN1bHRhZG8gZGVsIHRlc3QsIHBvZGVtb3MgY29ycm9ib3JhciBxdWUgbG9zIGludGVydmFsb3MgZGUgY29uZmlhbnphIGRlbCA5NSUgcGFyYSBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcyBubyBjb250aWVuZW4gYWwgMCBlbiBuaW5ndW5vIGRlIGxvcyBjYXNvcy4KClBhcmEgbGFzIHZhcmlhYmxlcyBkw61hcyBkZSBhY3RpdmlkYWQgZsOtc2ljYSBzZW1hbmFsIHkgY29uc3VtbyBkaWFyaW8gZGUgYWxjb2hvbDoKCi0gTGFzIHZhcmlhYmxlcyBOTyByZXN1bHRhbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyBwYXJhIGV4cGxpY2FyIGVsIHBlc28gZGUgbGFzIHBlcnNvbmFzIChwLXZhbG9yZXMgPiAwLjA1KS4KLSBBZGVtw6FzIGRlbCByZXN1bHRhZG8gZGVsIHRlc3QsIHBvZGVtb3MgY29ycm9ib3JhciBxdWUgbG9zIGludGVydmFsb3MgZGUgY29uZmlhbnphIGRlbCA5NSUgcGFyYSBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcyBjb250aWVuZW4gYWwgMCBlbiBsb3MgY2Fzb3MuCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CnRpZHkoYW5vdmEobW9kZWxvX2xuX2Jhc2UpKQpgYGAKCkxhIHRhYmxhIGRlIEFOT1ZBIG11ZXN0cmEgcXVlLCBzZWfDum4gZWwgcmVzdWx0YWRvIGRlbCB0ZXN0IEYsIGxhIHZhcmlhYmxlIGfDqW5lcm8gZW4gc3UgY29uanVudG8sIHRhbWJpw6luIHJlc3VsdGEgZXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2YSBwYXJhIGV4cGxpY2FyIGFsIHByZWNpbyAocC12YWxvciA8IDAuMDUpLgoKIyMjIyBUZXN0IEYgKHRlc3QgZGUgc2lnbmlmaWNhdGl2aWRhZCBnbG9iYWwpCgpTZSBjb25zdHJ1eWUgcGFyYSB0ZXN0ZWFyIGxhcyBoaXDDs3Rlc2lzOgoKSDA6zrIxPc6yMj3Ct8K3wrc9zrJw4oiSMT0wCgpIMTogbm8gdG9kb3MgbG9zIM6yayAoaz0xLDIsLi4uLHDiiJIxKSBzb24gaWd1YWxlcyBhIDAuCgpPYnNlcnZlbW9zIHF1ZSBIMCBkaWNlIHF1ZSBubyBoYXkgdsOtbmN1bG8gZW50cmUgbGEgdmFyaWFibGUgcmVzcHVlc3RhIHkgbGFzIHJlZ3Jlc29yYXMuIEVuIGNhbWJpbywgSDEgZGljZSBxdWUgYWwgbWVub3MgdW5hIGRlIGxhcyB2YXJpYWJsZXMgcmVncmVzb3JhcyBzaXJ2ZSBwYXJhIHByZWRlY2lyIGEgWS4KTG9zIHJlc3VsdGFkb3MgZGUgZXN0ZSB0ZXN0IHNlIHB1ZWRlbiBvYnNlcnZhciBoYWNpZW5kbyB1biBzdW1tYXJ5KCkgZGVsIG1vZGVsbyBvIGdsYW5jZSgpLgoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpnbGFuY2UobW9kZWxvX2xuX2Jhc2UpCmBgYAoKKkYtc3RhdGlzdGljOiA3NzAuNCBvbiA1IGFuZCA3MDE4IERGLCAgcC12YWx1ZTogPCAyLjJlLTE2KiAKCioqVGVzdCBzaWduaWZpY2F0aXZvKiogcG9yIGxvIHF1ZSBkZXRlcm1pbmFtb3MgcXVlIHNlIHJlY2hhemEgbGEgaGlwb3Rlc2lzIG51bGEsIG51ZXN0cm8gbW9kZWxvIGVzIHJlbGV2YW50ZSBlbiBjb21wYXJhY2nDs24gYWwgbW9kZWxvIGJhc2UsIHBhcmEgZGV0ZXJtaW5hciBxdWUgYXJyb2phIHJlc3VsdGFkb3Mgc2lnbmlmaWNhdGl2b3MgdG9tYW5kbyBsYSB2YXJpYWJsZSBwcmVkaWN0b3JhCgpFbCBSLWN1YWRyYWRvIHBlcm1pdGUgbWVkaXIgZWwgcG9yY2VudGFqZSBkZSB2YXJpYWJpbGlkYWQgZGVsIGZlbsOzbWVubyBxdWUgZWwgbW9kZWxvIGxvZ3JhIGV4cGxpY2FyLiBQb3IgZXN0ZSBtb3Rpdm8gZXMgdW5hIG3DqXRyaWNhIHF1ZSBub3MgcGVybWl0ZSBldmFsdWFyIGxhIGNhcGFjaWRhZCBleHBsaWNhdGl2YSAKZGVsIG1vZGVsbyB5IHBvZGVyIGNvbXBhcmFyIG1vZGVsb3MgZW50cmUgc8OtIGJham8gY2llcnRhcyBjb25kaWNpb25lcy4KCkVuIG51ZXN0cm8gY2FzbyB0ZW5lbW9zIHVuIHZhbG9yIGRlICoqTXVsdGlwbGUgUi1zcXVhcmVkOiAwLjM1NDQqKiwgKipBZGp1c3RlZCBSLXNxdWFyZWQ6IDAuMzUzOSoqIAoKPCEtLSAzKSBNb2RlbG8gY2F0ZWfDs3JpY2FzClNlIHN1Z2llcmUgcHJvYmFyIHVuIG1vZGVsbyBxdWUgaW5jb3BvcmUgZWwgY29uc3VtbyBzZW1hbmFsIGRlIHNuYWNrcyB5IHVuYSBpbnRlcmFjY2nDs24gZW50cmUgZWwgZ8OpbmVybyB5CmxhIGVkYWQsIGVuIGx1Z2FyIGRlIGFjdGl2aWRhZCBmw61zaWNhIHkgY29uc3VtbyBkZSBhbGNvaG9sOgpFKHBlc28pID0gzrIwICsgzrIxYWx0dXJhICsgzrIyZWRhZCArIM6yM2dlbmVybyArIM6yNGNvbnN1bW9TZW1hbmFsU25hY2tzICsgzrI1Z2VuZXJvIMK3IGVkYWQKQWRlbcOhcyBzZSBwaWRlIGV4cGzDrWNpdGFtZW50ZSBxdWUgbGEgY2F0ZWdvcsOtYSDigJxObyBjb23DrSBjb21pZGEgc2FsYWRhIG8gc25hY2tzIGVuIGxvcyDDumx0aW1vcyA3IGTDrWFz4oCdIGRlIGxhCnZhcmlhYmxlIGNvbnN1bW9TZW1hbmFsU25hY2tzIHNlIGVuY3VlbnRyZSBjb21vIG5pdmVsL2NhdGVnb3LDrWEgYmFzYWwuCsK/Q3XDoWwgZXMgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zIHBhcmEgbGFzIGNhdGVnb3LDrWFzIGRlIGNvbnN1bW9TZW1hbmFsU25hY2tzIHkKZ2VuZXJvIC4gZWRhZD8gwr9Tb24gc2lnbmlmaWNhdGl2YXM/IMK/UXXDqSBwb3JjZW50YWplIGRlIGxhIHZhcmlhYmlsaWRhZCBleHBsaWNhIGVsIG1vZGVsbz8KRW4gY2FzbyBkZSBkZXRlY3RhciBxdWUgZXhpc3RlbiBjYXRlZ29yw61hcyBubyBzaWduaWZpY2F0aXZhcyBkZSBsYSB2YXJpYWJsZSBjb25zdW1vU2VtYW5hbFNuYWNrcyBldmFsdWFyCnNpIGxhIHZhcmlhYmxlIGVzIHNpZ25pZmljYXRpdmEgZW4gc3UgY29uanVudG8geSwgZW4gY2FzbyBhZmlybWF0aXZvLCBwcm9wb25lciB1bmEgcmVkZWZpbmljacOzbiBkZSBsYXMgbWlzbWFzCnF1ZSBwZXJtaXRhIG9idGVuZXIgdW5hIG1heW9yIHByb3BvcmNpw7NuIGRlIGNhdGVnb3LDrWFzIHNpZ25pZmljYXRpdmFzIGluZGl2aWR1YWxtZW50ZS4gTHVlZ28sIGFuYWxpemFyIHNpCmV4aXN0ZW4gY2FtYmlvcyBlbiBsYSB2YXJpYWJpbGlkYWQgZXhwbGljYWRhIHBvciBlbCBtb2RlbG8uIC0tPgoKCiMjIE1vZGVsbyBjYXRlZ8Ozcmljb3MKCiMjIyBDcmVhY2nDs24gbW9kZWxvIGNhdGVnw7NyaWNvCgojIyMjIFNlIHN1Z2llcmUgcHJvYmFyIHVuIG1vZGVsbyBxdWUgaW5jb3BvcmUgZWwgY29uc3VtbyBzZW1hbmFsIGRlIHNuYWNrcyB5IHVuYSBpbnRlcmFjY2nDs24gZW50cmUgZWwgZ8OpbmVybyB5IGxhIGVkYWQsIGVuIGx1Z2FyIGRlIGFjdGl2aWRhZCBmw61zaWNhIHkgY29uc3VtbyBkZSBhbGNvaG9sOiAKIyMjIyBFKHBlc28pID0gzrIwICsgzrIxYWx0dXJhICsgzrIyZWRhZCArIM6yM2dlbmVybyArIM6yNGNvbnN1bW9TZW1hbmFsU25hY2tzICsgzrI1Z2VuZXJvIMK3IGVkYWQKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCmVuY3Vlc3RhX3NhbHVkX3RyYWluICU+JSBnbGltcHNlCgplbmN1ZXN0YV9zYWx1ZF9zZWNvbmRfbW9kZWwgPSBlbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChlZGFkLCBnZW5lcm8sIGFsdHVyYSwgcGVzbyxjb25zdW1vX3NlbWFuYWxfc25hY2tzKQoKZW5jdWVzdGFfc2FsdWRfc2Vjb25kX21vZGVsICU+JSBnbGltcHNlCgpgYGAKCkRlamFtb3MgYWwgdmFsb3IgKk5vIGNvbcOtIGNvbWlkYSBzYWxhZGEgbyBzbmFja3MgZW4gbG9zIMO6bHRpbW9zIDcgZMOtYXMqIGRlIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3MgY29tbyBjYXRlZ29yw61hIGJhc2FsIGVuIHVuYSBudWV2YSB2YXJpYWJsZSBxdWUgbGxhbWFyZW1vcyAqKmNvbnN1bW9fc2VtYW5hbF9zbmFja3NfcmVsZXZlbCoqCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZW5jdWVzdGFfc2FsdWRfc2Vjb25kX21vZGVsJGNvbnN1bW9fc2VtYW5hbF9zbmFja3NfcmVsZXZlbCA8LSByZWxldmVsKGFzLmZhY3RvcihlbmN1ZXN0YV9zYWx1ZF9zZWNvbmRfbW9kZWwkY29uc3Vtb19zZW1hbmFsX3NuYWNrcyksIHJlZiA9IDgpCmBgYAoKR3JhZmljw6Ftb3MgbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgKmNvbnN1bW9TZW1hbmFsU25hY2tzKiBwYXJhIGFuYWxpemFyIHN1cyB2YWxvcmVzIGVuIGZ1bmNpw7NuIGRlbCBwZXNvCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCiMgYXJtbyBib3hwbG90cyBwYXJhbGVsb3MgY29uc3VtbyBkZSBzbmFja3MgZW4gZnVuY2nDs24gZGVsIHBlc28KZ2dwbG90KCBlbmN1ZXN0YV9zYWx1ZF9zZWNvbmRfbW9kZWwsIGFlcyh4ID0gZmN0X3Jlb3JkZXIoY29uc3Vtb19zZW1hbmFsX3NuYWNrc19yZWxldmVsLCBwZXNvLCAuZGVzYyA9IFQpLCB5ID0gcGVzbykpICsgCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC43NSwgYWVzKGZpbGwgPSBjb25zdW1vX3NlbWFuYWxfc25hY2tzX3JlbGV2ZWwpKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykrCiAgbGFicyh5ID0gIlBlc28iLCB4ID0gIkNvbnN1bW8gc25hY2tzIikgICsKICBnZ3RpdGxlKCJCb3hwbG90cyBkZWwgcGVzbyBlbiBmdW5jacOzbiBkZWwgY29uc3VtbyBkZSBzbmFja3MiKSsKICB0aGVtZSAoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZT0iaXRhbGljIiwgY29sb3VyPSJkYXJrIGdyZXkiLCBzaXplID0gOCwgYW5nbGUgPSA5MCkpCgpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbW9kZWxvX2xuXzJfciA8LSBsbShwZXNvIH4gY29uc3Vtb19zZW1hbmFsX3NuYWNrc19yZWxldmVsICsgZWRhZCArIGdlbmVybyArIGFsdHVyYSArIChlZGFkICogZ2VuZXJvKSwgZGF0YSA9IGVuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbCkKYGBgCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiMgUmVzdW1lbiBkZWwgbW9kZWxvCnRpZHlfbG5fMl9yIDwtIHRpZHkobW9kZWxvX2xuXzJfciwgY29uZi5pbnQgPSBUUlVFKQpwcmludCh0aWR5X2xuXzJfcikKYGBgCgoKIyMjIEludGVycHJldGFjacOzbiBkZSBjb2VmaWNpZW50ZXMgLyBBbsOhbGlzaXMgZGUgc2lnbmlmaWNhbmNpYQoKSW50ZXJjZXB0CgotIEVsIHZhbG9yIGRlIM6yMCAob3JkZW5hZGEgYWwgb3JpZ2VuKSBlcyAtNjQuMiBrZywgbG8gcXVlIGNvcnJlc3BvbmRlIGFsIHBlc28gZXNwZXJhZG8gZGUgdW5hIHBlcnNvbmEgZmVtZW5pbmEsIHF1ZSBubyBjb21pw7MgY29taWRhIHNhbGFkYSBvIHNuYWNrcyBlbiBsb3Mgw7psdGltb3MgNyBkaWFzIHkgY29udGllbmUKbGEgc3VtYSBkZWwgY29lZmljaWVudGUgKmVkYWQ6Z2VuZXJvRmVtZW5pbm8qIG1hbnRlbmllbmRvIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgY29uc3RhbnRlcy4gKFVuYSB2YXJpYWJsZSBkZSBjYWRhIGF0cmlidXRvIGNhdGVnb3LDs2NvIGZvcm1hbiBwYXJ0ZSBkZWwgSW50ZXJjZXB0LCBlbiBlc3RlIGNhc28gCmfDqW5lcm8gKmZlbWVuaW5vKiwgYXF1ZWxsYSBxdWUgcmVkZWZpbmltb3MgcGFyYSBxdWUgc2VhIGJhc2FsIGRlIGNvbnN1bW8gZGUgc25hY2tzICpObyBjb21pw7MgY29taWRhIHNhbGFkYSBvIHNuYWNrcyBlbiBsb3Mgw7psdGltb3MgNyBkaWFzKiB5IGxhIG51ZXZhIHZhcmlhYmxlIGNyZWFkYSAqZWRhZDpnZW5lcm9GZW1lbmlubyopCgpDb2VmaWNpZW50ZSBlZGFkOmdlbmVyb01hc2N1bGlubzoKCi0gRWwgY29lZmljaWVudGUgZXN0aW1hZG8gZGUgZWRhZDpnZW5lcm9NYXNjdWxpbm8gZXMgZGUgMC4zOTFrZywgbG8gcXVlIGluZGljYSBxdWUgc2kgbWFudGVuZW1vcyBlbCBuw7ptZXJvIGRlIGFsdHVyYSwgZ2VuZXJvLCBlZGFkIHkgY29uc3VtbyBzZW1hbmFsIGRlIHNuYWNrcywgCmNhZGEgaW5jcmVtZW50byBkZSB1bmEgdW5pZGFkIHBhcmEgZWRhZDpnZW5lcm9NYXNjdWxpbm8gY29ycmVzcG9uZGUgYSB1biBhdW1lbnRvIGRlIDAuMzkxa2csIGVuIHByb21lZGlvIGVuIGVsIHBlc28gZGUgbGEgcGVyc29uYSBmZW1lbmluYS4gPT4gKipTaWduaWZpY2F0aXZhKioKCkNvZWZpY2llbnRlIGRlbCBjb25zdW1vIHNlbWFuYWwgZGUgc25hY2tzOgoKLSBFbCBjb2VmaWNpZW50ZSBlc3RpbWFkbyBkZSBjb25zdW1vIHNlbWFuYWwgc25hY2tzIDEgYSAzIHZlY2VzIGR1cmFudGUgbG9zIMO6bHRpbW9zIDcgZMOtYXMgZXMgZGUgLTEuMzUsIHNpIG1hbnRlbmVtb3MgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBjb25zdGFudGVzLCBlbCBpbmNyZW1lbnRvIGRlIHVuYSB1bmlkYWQgcGFyYSAKY29uc3Vtb19zZW1hbmFsX3NuYWNrcyAxIGEgMyB2ZWNlcyBkdXJhbnRlIGxvcyDDumx0aW1vcyA3IGTDrWFzLCBjb3JyZXNwb25kZSBhIHVuYSBiYWphIGRlIDEuMzVrZywgZW4gcHJvbWVkaW8gZW4gZWwgcGVzbyBkZSBsYSBwZXJzb25hLiA9PiAqKlNpZ25pZmljYXRpdmEqKgoKLSBFbCBjb2VmaWNpZW50ZSBlc3RpbWFkbyBkZSBjb25zdW1vX3NlbWFuYWxfc25hY2tzIDEgdmV6IGFsIGTDrWEgZXMgZGUgLTAuNjA4LCBzaSBtYW50ZW5lbW9zIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgY29uc3RhbnRlcywgZWwgaW5jcmVtZW50byBkZSB1bmEgdW5pZGFkIHBhcmEgCmNvbnN1bW9fc2VtYW5hbF9zbmFja3MgMSB2ZXogYWwgZMOtYSBjb3JyZXNwb25kZSBhIHVuYSBiYWphIGRlIDAuNjA4LCBlbiBwcm9tZWRpbyBlbiBlbCBwZXNvIGRlIGxhIHBlcnNvbmEuICoqTm8gc2lnbmlmaWNhdGl2YSoqCgotIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgMiB2ZWNlcyBhbCBkw61hIGVzIGRlIC0xLjA5LCBzaSBtYW50ZW5lbW9zIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgY29uc3RhbnRlcywgZWwgaW5jcmVtZW50byBkZSB1bmEgdW5pZGFkIHBhcmEgCmNvbnN1bW9fc2VtYW5hbF9zbmFja3MgMiB2ZWNlcyBhbCBkw61hIGNvcnJlc3BvbmRlIGEgdW5hIGJhamEgZGUgMS4wOSwgZW4gcHJvbWVkaW8gZW4gZWwgcGVzbyBkZSBsYSBwZXJzb25hLiA9PiAqKk5vIHNpZ25pZmljYXRpdmEqKgoKLSBFbCBjb2VmaWNpZW50ZSBlc3RpbWFkbyBkZSBjb25zdW1vX3NlbWFuYWxfc25hY2tzIDMgdmVjZXMgYWwgZMOtYSBlcyBkZSAtMS4yOCwgc2kgbWFudGVuZW1vcyBlbCByZXN0byBkZSBsYXMgdmFyaWFibGVzIGNvbnN0YW50ZXMsIGVsIGluY3JlbWVudG8gZGUgdW5hIHVuaWRhZCBwYXJhIApjb25zdW1vX3NlbWFuYWxfc25hY2tzIDMgdmVjZXMgYWwgZMOtYSBjb3JyZXNwb25kZSBhIHVuYSBiYWphIGRlIDEuMjgsIGVuIHByb21lZGlvIGVuIGVsIHBlc28gZGUgbGEgcGVyc29uYS4gPT4gKipObyBzaWduaWZpY2F0aXZhKioKCi0gRWwgY29lZmljaWVudGUgZXN0aW1hZG8gZGUgY29uc3Vtb19zZW1hbmFsX3NuYWNrcyA0IGEgNiB2ZWNlcyBkdXJhbnRlIGxvcyDDumx0aW1vcyA3IGTDrWFzIGVzIGRlIC0yLjI3LCBzaSBtYW50ZW5lbW9zIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgY29uc3RhbnRlcywgZWwgaW5jcmVtZW50byBkZSB1bmEgdW5pZGFkIHBhcmEgCmNvbnN1bW9fc2VtYW5hbF9zbmFja3MgNCBhIDYgdmVjZXMgZHVyYW50ZSBsb3Mgw7psdGltb3MgNyBkw61hcyBjb3JyZXNwb25kZSBhIHVuYSBiYWphIGRlIDIuMjdrZywgZW4gcHJvbWVkaW8gZW4gZWwgcGVzbyBkZSBsYSBwZXJzb25hLiA9PiAqKlNpZ25pZmljYXRpdmEqKgoKLSBFbCBjb2VmaWNpZW50ZSBlc3RpbWFkbyBkZSBjb25zdW1vX3NlbWFuYWxfc25hY2tzIDQgbyBtw6FzIHZlY2VzIGFsIGTDrWEgZXMgZGUgLTIuNTcsIHNpIG1hbnRlbmVtb3MgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBjb25zdGFudGVzLCBlbCBpbmNyZW1lbnRvIGRlIHVuYSB1bmlkYWQgcGFyYSBlc3RpbWFkbyBkZSAKY29uc3Vtb19zZW1hbmFsX3NuYWNrcyA0IG8gbcOhcyB2ZWNlcyBhbCBkw61hIGNvcnJlc3BvbmRlIGEgdW5hIGJhamEgZGUgMi41N2tnLCBlbiBwcm9tZWRpbyBlbiBlbCBwZXNvIGRlIGxhIHBlcnNvbmEuID0+ICoqU2lnbmlmaWNhdGl2YSoqCgotIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgRGF0byBwZXJkaWRvIGVzIGRlIC00LjQ0LCBzaSBtYW50ZW5lbW9zIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgY29uc3RhbnRlcywgZWwgaW5jcmVtZW50byBkZSB1bmEgdW5pZGFkIHBhcmEgCmNvbnN1bW9fc2VtYW5hbF9zbmFja3NEYXRvIHBlcmRpZG8gY29ycmVzcG9uZGUgYSB1bmEgYmFqYSBkZSA0LjQ0a2csIGVuIHByb21lZGlvIGVuIGVsIHBlc28gZGUgbGEgcGVyc29uYS4gPT4gKipTaWduaWZpY2F0aXZhKioKCkxvcyBjb2VmaWNpZW50ZXMgcGFyYSBsYXMgZGlmZXJlbnRlcyBjYXRlZ29yaWFzIGRlIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3MgYWxndW5vcyBzb24gc2lnbmlmaWNhdGl2b3MgeSBvdHJvcyBuby4KClJlYWxpemFtb3MgZWwgdGVzdCBkZSBhbm92YSBwYXJhIHZlciBsYSBzaWduaWZpY2FuY2lhIGRlIGxhcyB2YXJpYWJsZXMgZW4gc3UgY29uanVudG8KCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KdGlkeShhbm92YShtb2RlbG9fbG5fMl9yKSkKYGBgCgpMYSB0YWJsYSBkZSBBTk9WQSBtdWVzdHJhIHF1ZSwgc2Vnw7puIGVsIHJlc3VsdGFkbyBkZWwgdGVzdCBGLCAqKmxhIHZhcmlhYmxlIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgZW4gc3UgY29uanVudG8gcmVzdWx0YSBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhIHBhcmEgZXhwbGljYXIgYWwgcHJlY2lvIChwLXZhbG9yIDwgMC4wNSkqKi4KCiMjIyBSZWRlZmluaWNpb24gZGUgY2F0ZWfDs3JpY2FzCgpDcmVhbW9zIHVuYSB2YXJpYWJsZSBudWV2YSBxdWUgYWdydXBlIGxhcyB0cmVzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgcXVlIG5vcyBkaWVyb24gdW5hIE5PIHNpZ25pZmljYW5jaWEgZW4gZWwgYW7DoWxpc2lzIHByZXZpbywgbGFzIG1pc21hcyBzZXJpYW4KCi0gWzNdICJjb25zdW1vX3NlbWFuYWxfc25hY2tzX3JlbGV2ZWwxIHZleiBhbCBkw61hIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAoKLSBbNF0gImNvbnN1bW9fc2VtYW5hbF9zbmFja3NfcmVsZXZlbDIgdmVjZXMgYWwgZMOtYSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCgotIFs1XSAiY29uc3Vtb19zZW1hbmFsX3NuYWNrc19yZWxldmVsMyB2ZWNlcyBhbCBkw61hIiAgICAgCgoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQoKZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lIGdsaW1wc2UKCmVuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbF9hZ3J1cGFkbyA9IGVuY3Vlc3RhX3NhbHVkX3RyYWluICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNlbGVjY2lvbmFtb3MgbGFzIHZhcmlhYmxlcyBkZSBpbnRlcsOpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGVkYWQsIGdlbmVybywgYWx0dXJhLCBwZXNvLGNvbnN1bW9fc2VtYW5hbF9zbmFja3MpCgplbmN1ZXN0YV9zYWx1ZF9zZWNvbmRfbW9kZWxfYWdydXBhZG8kY29uc3Vtb19zZW1hbmFsX3NuYWNrc19yZWxldmVsIDwtIHJlbGV2ZWwoYXMuZmFjdG9yKGVuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbF9hZ3J1cGFkbyRjb25zdW1vX3NlbWFuYWxfc25hY2tzKSwgcmVmID0gOCkKCgpgYGAKClJlZGVmaW5pbW9zIGxhIHZhcmlhYmxlIGFncnVwYW5kbyBsb3MgdmFsb3JlcyBjYXRlZ29yaXphZG9zIGNvbW8gMSB2ZXogYWwgZMOtYSwgMiB2ZWNlcyBhbCBkw61hIHkgdHJlcyB2ZWNlcyBhbCBkw61hLCBlbiAxIGEgMyB2ZWNlcyBhbCBkaWEuCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCmVuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbF9hZ3J1cGFkbyA9IGVuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbF9hZ3J1cGFkbyAlPiUgbXV0YXRlKGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgPSBjYXNlX3doZW4oc3RhcnRzV2l0aChjb25zdW1vX3NlbWFuYWxfc25hY2tzLCAiMSB2ZXogYWwgZMOtYSIpIH4gIjEgYSAzIHZlY2VzIGFsIGRpYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c1dpdGgoY29uc3Vtb19zZW1hbmFsX3NuYWNrcywgIjIgdmVjZXMgYWwgZMOtYSIpIH4gImNvbnN1bW9fc2VtYW5hbF9zbmFja3MgMSBhIDMgdmVjZXMgYWwgZGlhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzV2l0aChjb25zdW1vX3NlbWFuYWxfc25hY2tzLCAiMyB2ZWNlcyBhbCBkw61hIikgfiAiY29uc3Vtb19zZW1hbmFsX3NuYWNrcyAxIGEgMyB2ZWNlcyBhbCBkaWEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gY29uc3Vtb19zZW1hbmFsX3NuYWNrcykpCmBgYAoKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KI2VuY3Vlc3RhX3NhbHVkX3NlY29uZF9tb2RlbF9hZ3J1cGFkbyRjb25zdW1vX3NlbWFuYWxfc25hY2tzCmBgYAoKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbW9kZWxvX2xuXzJfcl9hZ3J1cGFkbyA8LSBsbShwZXNvIH4gY29uc3Vtb19zZW1hbmFsX3NuYWNrcyArIGVkYWQgKyBnZW5lcm8gKyBhbHR1cmEgKyAoZWRhZCAqIGdlbmVybyksIGRhdGEgPSBlbmN1ZXN0YV9zYWx1ZF9zZWNvbmRfbW9kZWxfYWdydXBhZG8pCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIFJlc3VtZW4gZGVsIG1vZGVsbwp0aWR5X2xuXzJfcl9hZ3J1cGFkbyA8LSB0aWR5KG1vZGVsb19sbl8yX3JfYWdydXBhZG8sIGNvbmYuaW50ID0gVFJVRSkKcHJpbnQodGlkeV9sbl8yX3JfYWdydXBhZG8pCmBgYAoKT2JzZXJ2YW1vcyBxdWUgbGEgcmVkZWZpbmljacOzbiBkZSBsYXMgdmFyaWFibGVzIG5vIHNpZ25pZmljYXRpdmFzIGdlbmVyYW5kbyB1biBhZ3J1cGFtaWVudG8gcGFyYSBkZSBsYXMgbWlzbWFzIG1hbnRpZW5lIGxhIG5vIHNpZ25pZmljYW5jaWEgZGUgZXN0b3MgdmFsb3JlcyBwYXJhIHByZWRlY2lyIGVsIHBlc28KZGUgdW5hIHBlcnNvbmEuCgojIyMgRXZhbHVhY2nDs24gZGUgbW9kZWxvCgpFamVjdXRhbW9zIGxhcyBmdW5jaW5lcyB0aWR5LCBnbGFuY2UgeSBzdW1tYXJ5IHBhcmEgb2J0ZW5lciB0YW50byBsYSBzaWduaWZpY2FjaWEgZGUgbG9zIGF0cmlidXRvcyBkZWwgbW9kZWxvLCBsYSBzaWduaWZpY2FuY2lhIGRlbCBtb2RlbG8gZW4gc2kgKFRlc3QgRikgeSBsYSB2YXJpYWJpbGlkYWQgZGVsIAptaXNtbyAoUi1jdWFkcmFkbyB5IFItY3VhZHJhZG8gYWp1c3RhZG8pLgoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpvcHRpb25zKCJzY2lwZW4iPTEpCnRpZHlfbG5fMl9yICU+JQogIGRwbHlyOjpzZWxlY3QodGVybSwgc3RhdGlzdGljLCBwLnZhbHVlLCBjb25mLmxvdywgY29uZi5oaWdoKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZ2xhbmNlKG1vZGVsb19sbl8yX3IpCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpzdW1tYXJ5KG1vZGVsb19sbl8yX3IpCmBgYAoKRWwgcG9yY2VudGFqZSBkZSB2YXJpYWJpbGlkYWQgcXVlIGV4cGxpY2EgZXN0ZSBudWV2byBtb2RlbG8gZXMgZGUgKipSLWN1YWRyYWRvID0gMC4zNTkqKiwgKipSLWN1YWRyYWRvIGFqdXN0YWRvID0gMC4zNTgqKgpFdmFsdWFuZG8gbGEgc2lnbmlmaWNhdGl2aWRhIGRlbCBtb2RlbG8sIG9idGVuZW1vcyBGLXN0YXRpc3RpYzogMzU2LjMgb24gMTEgYW5kIDcwMTIgREYsICBwLXZhbHVlOiA8IDIuMmUtMTYgKipNb2RlbG8gc2lnbmlmaWNhdGl2byoqCgo8IS0tIDQpIE1vZGVsb3MgcHJvcGlvcyB5IGV2YWx1YWNpw7NuClJlYWxpemFyIDIgbW9kZWxvcyBsaW5lYWxlcyBtw7psdGlwbGVzIGFkaWNpb25hbGVzIHkgZXhwbGljYXIgYnJldmVtZW50ZSBsYSBsw7NnaWNhIGRldHLDoXMgZGUgbG9zIG1pc21vcyAoc2UKdmFsb3JhcsOhIGxhIGNyZWFjacOzbiB5L28gaW5jbHVzacOzbiBkZSB2YXJpYWJsZXMgbnVldmFzKS4KRXZhbHVhciBsYSBwZXJmb3JtYW5jZSBkZWwgbW9kZWxvIGluaWNpYWwsIGVsIG1vZGVsbyBjYXRlZ8OzcmljYXMgY29uIGxhcyBjYXRlZ29yw61hcyByZWRlZmluaWRhcyBkZSBsYQp2YXJpYWJsZSBjb25zdW1vU2VtYW5hbFNuYWNrcyB5IGxvcyBtb2RlbG9zIGRlc2Fycm9sbGFkb3MgZW4gZXN0ZSBwdW50byBlbiBlbCBkYXRhc2V0IGRlIGVudHJlbmFtaWVudG8geQpldmFsdWFjacOzbiAodXNhciBkYXRhc2V0IOKAnGVuY3Vlc3RhX3NhbHVkX3Rlc3QuY3N24oCdKS4gTGEgZXZhbHVhY2nDs24gZGUgcGVyZm9ybWFuY2UgY29uc2lzdGUgZW4gY29tcGFyYXIKbGEgcGVyZm9ybWFuY2UgZW4gdMOpcm1pbm9zIGRlbCBSIGN1YWRyYWRvIGFqdXN0YWRvLCBSTVNFIHkgTUFFIHNvYnJlIGVsIHNldCBkZSBlbnRyZW5hbWllbnRvIHkgZW4KdMOpcm1pbm9zIGRlIFJNU0UgeSBNQUUgc29icmUgZWwgc2V0IGRlIGV2YWx1YWNpw7NuLgrCv0N1w6FsIGVzIGVsIG1lam9yIG1vZGVsbyBwYXJhIG51ZXN0cm8gb2JqZXRpdm8gZGUgcHJlZGVjaXIgZWwgcGVzbz8gwr9Qb3IgcXXDqT8gLS0+Cgo8IS0tIArCv0PDs21vIHNlIGNhbGN1bGEgbWkgSU1DPwrCv0PDs21vIHNlIGNhbGN1bGEgZWwgSU1DPyBDb24gZWwgc2lzdGVtYSBtw6l0cmljbywgbGEgZsOzcm11bGEgcGFyYSBlbCBJTUMgZXMgZWwgcGVzbyBlbiBraWxvZ3JhbW9zIGRpdmlkaWRvIHBvciBsYSBlc3RhdHVyYSBlbiBtZXRyb3MgY3VhZHJhZG9zLiBEZWJpZG8gYSBxdWUgbGEgZXN0YXR1cmEgcG9yIGxvIGdlbmVyYWwgc2UgbWlkZSBlbiBjZW50w61tZXRyb3MsIGRpdmlkYSBsYSBlc3RhdHVyYSBlbiBjZW50w61tZXRyb3MgcG9yIDEwMCBwYXJhIG9idGVuZXIgbGEgZXN0YXR1cmEgZW4gbWV0cm9zLgotLT4KCgojIyBNb2RlbG9zIHByb3Bpb3MgeSBldmFsdWFjacOzbgoKIyMjIENyZWFjacOzbiBkZSBudWV2b3MgbW9kZWxvcwoKUHJvcG9uZW1vcyB1biBtb2RlbG8gcXVlIGV2YWx1ZSBlbCBwZXNvIGEgcGFydGlyIGRlbCBuaXZlbCBlZHVjYXRpdm8sIGNvbnN1bW9fc2VtYW5hbF9jb21pZGFfZ3Jhc2EsIGVkYWQsIGdlbmVybywgYWx0dXJhIHkgdW5hIGludGVycmVsYWNpw7NuIGRlIGFsdHVyYSwgZWRhZCB5IGfDqW5lcm8uCgoqKmYocGVzbykgfiBuaXZlbF9lZHVjYXRpdm8gKyBjb25zdW1vX3NlbWFuYWxfY29taWRhX2dyYXNhICsgZWRhZCArIGdlbmVybyArIGFsdHVyYSArIChhbHR1cmEgKiBlZGFkICogZ2VuZXJvKSoqCgpTZWxlY2Npb25hbW9zIG51ZXN0cmFzIHZhcmlhYmxlcyBkZSBpbnRlcsOpcwoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQplbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUgZ2xpbXBzZQoKZW5jdWVzdGFfc2FsdWRfdGhpcmRfbW9kZWwgPSBlbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChlZGFkLCBnZW5lcm8sIGFsdHVyYSwgcGVzbyxuaXZlbF9lZHVjYXRpdm8sY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYSkKCmVuY3Vlc3RhX3NhbHVkX3RoaXJkX21vZGVsICU+JSBnbGltcHNlCgpgYGAKCkdlbmVyYW1vcyBlbCBtb2RlbG8gbGluZWFsCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbW9kZWxvX2xuXzNfciA8LSBsbShwZXNvIH4gbml2ZWxfZWR1Y2F0aXZvICsgY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYSArIGVkYWQgKyBnZW5lcm8gKyBhbHR1cmEgKyAoYWx0dXJhICogZWRhZCAqIGdlbmVybyksIGRhdGEgPSBlbmN1ZXN0YV9zYWx1ZF90aGlyZF9tb2RlbCkKIyBSZXN1bWVuIGRlbCBtb2RlbG8KdGlkeV9sbl8zX3IgPC0gdGlkeShtb2RlbG9fbG5fM19yLCBjb25mLmludCA9IFRSVUUpCnByaW50KHRpZHlfbG5fM19yKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZ2xhbmNlKG1vZGVsb19sbl8zX3IpCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQp0aWR5KGFub3ZhKG1vZGVsb19sbl8zX3IpKQpgYGAKClByb3BvbmVtb3MgdW4gbW9kZWxvIHF1ZSBldmFsdWUgZWwgcGVzbyBhIHBhcnRpciBkZWwgbml2ZWwgZWR1Y2F0aXZvLCBjb25zdW1vX3NlbWFuYWxfY29taWRhX2dyYXNhLCBnZW5lcm8sIGFsdHVyYSB5IHVuYSBpbnRlcnJlbGFjacOzbiBkZSBkaWFzIGNvbnN1bW8gY29taWRhIHLDoXBpZGEgeSBnZW5lcm8KKipmKHBlc28pIH4gbml2ZWxfZWR1Y2F0aXZvICsgY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYSArIGdlbmVybyArIGFsdHVyYSArIChkaWFzX2NvbnN1bW9fY29taWRhX3JhcGlkYSAqIGdlbmVybykqKgoKU2VsZWNjaW9uYW1vcyBudWVzdHJhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQplbmN1ZXN0YV9zYWx1ZF90cmFpbiAlPiUgZ2xpbXBzZQoKZW5jdWVzdGFfc2FsdWRfZm91cnRoX21vZGVsID0gZW5jdWVzdGFfc2FsdWRfdHJhaW4gJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2VsZWNjaW9uYW1vcyBsYXMgdmFyaWFibGVzIGRlIGludGVyw6lzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoZWRhZCwgZ2VuZXJvLCBhbHR1cmEsIHBlc28sbml2ZWxfZWR1Y2F0aXZvLGRpYXNfY29uc3Vtb19jb21pZGFfcmFwaWRhKQoKZW5jdWVzdGFfc2FsdWRfZm91cnRoX21vZGVsICU+JSBnbGltcHNlCgpgYGAKCkdlbmVyYW1vcyBudWVzdHJvIG1vZGVsbyBsaW5lYWwKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQptb2RlbG9fbG5fNF9yIDwtIGxtKHBlc28gfiBuaXZlbF9lZHVjYXRpdm8gKyBkaWFzX2NvbnN1bW9fY29taWRhX3JhcGlkYSArIGdlbmVybyArIGFsdHVyYSArIChkaWFzX2NvbnN1bW9fY29taWRhX3JhcGlkYSAqIGdlbmVybyksIGRhdGEgPSBlbmN1ZXN0YV9zYWx1ZF9mb3VydGhfbW9kZWwpCiMgUmVzdW1lbiBkZWwgbW9kZWxvCnRpZHlfbG5fNF9yIDwtIHRpZHkobW9kZWxvX2xuXzRfciwgY29uZi5pbnQgPSBUUlVFKQpwcmludCh0aWR5X2xuXzRfcikKYGBgCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmdsYW5jZShtb2RlbG9fbG5fNF9yKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KdGlkeShhbm92YShtb2RlbG9fbG5fNF9yKSkKYGBgCgojIyMgRXZhbHVhY2nDs24gZGUgbW9kZWxvcwoKQWdydXBhbW9zIHRvZG9zIGxvcyBtb2RlbG9zIHF1ZSBub3MgaW50ZXJlc2EgY29tcGFyYXIsIGVuIGVzdGUgY2FzbyBlcyBlbCBtb2RlbG8gaW5pY2lhbCwgZWwgbW9kZWxvIGNhdGVnw7NyaWNhcyBjb24gbGFzIGNhdGVnb3LDrWFzIHJlZGVmaW5pZGFzIGRlIGxhCnZhcmlhYmxlIGNvbnN1bW9TZW1hbmFsU25hY2tzIHkgbG9zIGRvcyBtb2RlbG9zIGRlc2Fycm9sbGFkb3MgZW4gZXN0ZSBwdW50bywgaW50ZXJyZWxhY2nDs24gKGFsdHVyYSAqIGVkYWQgKiBnZW5lcm8pIHkgKGRpYXNfY29uc3Vtb19jb21pZGFfcmFwaWRhICogZ2VuZXJvKQoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIGFybWFtb3MgbGlzdGEgY29uIHRvZG9zIGxvcyBtb2RlbG9zCm1vZGVscyA8LSBsaXN0KG1vZGVsb19sbl80X3IgPSBtb2RlbG9fbG5fNF9yLCBtb2RlbG9fbG5fM19yID0gbW9kZWxvX2xuXzNfciwgbW9kZWxvX2xuXzJfciA9IG1vZGVsb19sbl8yX3IsIG1vZGVsb19sbl9iYXNlID0gbW9kZWxvX2xuX2Jhc2UpCiMgY2FsY3VsYW1vcyBsYXMgdmFyaWFibGVzIHJlc3VtZW4KbWFwX2RmKG1vZGVscywgdGlkeSwgLmlkID0gIm1vZGVsIikKYGBgCgojIyMjIFNlbGVjY2nDs24gZGVsIG1lam9yIG1vZGVsbyBlbiB0cmFpbmluZwoKVXRpbGl6YW1vcyBsYSBmdW5jacOzbiBhdWdtZW50IHBhcmEgcHJlZGVjaXIgZWwgcGVzbyBzb2JyZSBlbCBkYXRhc2V0IGRlIHRyYWluaW5nCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbGlzdGFfcHJlZGljY2lvbmVzX3RyYWluaW5nID0gbWFwKC54ID0gbW9kZWxzLCAuZiA9IGF1Z21lbnQpIApgYGAKCkNhbGN1bGFtb3MgZWwgUk1TRSBwYXJhIHRvZG9zIGxvcyBtb2RlbG9zIGEgZXZhbHVhcgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9Cm1hcF9kZnIoLnggPSBsaXN0YV9wcmVkaWNjaW9uZXNfdHJhaW5pbmcsIC5mID0gcm1zZSwgdHJ1dGggPSBwZXNvLCBlc3RpbWF0ZSA9IC5maXR0ZWQsIC5pZD0ibW9kZWxvIikgJT4lIGFycmFuZ2UoLmVzdGltYXRlKQpgYGAKCkNhbGN1bGFtb3MgZWwgTUFFIHBhcmEgdG9kb3MgbG9zIG1vZGVsb3MgYSBldmFsdWFyCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbWFwX2RmcigueCA9IGxpc3RhX3ByZWRpY2Npb25lc190cmFpbmluZywgLmYgPSBtYWUsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSBhcnJhbmdlKC5lc3RpbWF0ZSkKYGBgCgpPYnRlbmVtb3MgZWwgUi1jdWFkcmFkbyB5IFItY3VhZHJhZG8gYWp1c3RhZG8gcGFyYSBwb2RlciBjb21wYXJhciBsYSB2YXJpYWJpbGlkYWQgZGUgbG9zIG1vZGVsb3MKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpkZl9ldmFsdWFjaW9uX3RyYWluID0gbWFwX2RmKG1vZGVscywgZ2xhbmNlLCAuaWQgPSAibW9kZWwiKSAlPiUKICAjIG9yZGVuYW1vcyBwb3IgUjIgYWp1c3RhZG8KICBhcnJhbmdlKGRlc2MoYWRqLnIuc3F1YXJlZCkpCgpkZl9ldmFsdWFjaW9uX3RyYWluCmBgYAoKQ29uY2x1aW1vcyBzb2JyZSBlbCBkYXRhc2V0IGRlIHRyYWluaW5nCgotIEFuYWxpemFuZG8gZWwgUi1jdWFkcmFkbyBhanVzdGFkbywgZWwgbWVqb3IgbW9kZWxvIHJlc3VsdGEgc2VyIG1vZGVsb19sbl8yX3IgcXVlIGV4cGxpY2EgZWwgcGVzbyBlbiBmdW5jacOzbiBkZSBsYSBhbHR1cmEsIGVkYWQsIGfDqW5lcm8sIGRpYXMgZGUgYWN0aXZpZGFkIGbDrXNpY2Egc2VtYW5hbCB5IGNvbnN1bW8gZGlhcmlvIGRlIGFsY29ob2wKLSBFc3RlIG1vZGVsbyAobW9kZWxvX2xuXzJfcikgZXhwbGljYSAzNSw4JSBkZSBsYSB2YXJpYWJpbGlkYWQgZGVsIHBlc28sIGVzIGRlY2lyLCBtw6FzIHF1ZSB0b2RvcyBsb3MgcmVzdGFudGVzIG1vZGVsb3MuCi0gRWwgbW9kZWxvIGNvbiBtZW5vciBSTVNFIGVzIGVsIG1vZGVsb19sbl8zX3IgY29uIHVuIHZhbG9yIGRlIDkuODcsIG5vIGNvaW5jaWRlIGNvbiBlbCBtZWpvciBSLWN1YWRyYWRvIGFqdXN0YWRvLCBwZXJvIHNpIHBvciB1bmEgZGlmZXJlbmNpYSBtw61uaW1hIG1vZGVsb19sbl8yX3IgKDkuODgpCi0gRW4gY3VhbnRvIGFsIE1BRSwgdGFudG8gZWwgbW9kZWxvIG1vZGVsb19sbl8yX3IgY29tbyBtb2RlbG9fbG5fM19yLCBzb24gbG9zIHF1ZSB0aWVuZW4gbWVqb3JlcyByZXN1bHRhZG9zIGNvbiA3LjQzCgojIyMjIEV2YWx1YWNpw7NuIGVuIGVsIGRhdGFzZXQgZGUgdGVzdGluZwoKVXRpbGl6YW1vcyBsYSBmdW5jacOzbiBhdWdtZW50IHBhcmEgcHJlZGVjaXIgZWwgcGVzbyBzb2JyZSBlbCBkYXRhc2V0IGRlIHRyYWluaW5nCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KIyBBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYXVnbWVudCBhIGxvcyA0IG1vZGVsb3MgY29uIGVsIHNldCBkZSB0ZXN0aW5nCmVuY3Vlc3RhX3NhbHVkX3Rlc3QkY29uc3Vtb19zZW1hbmFsX3NuYWNrc19yZWxldmVsIDwtIHJlbGV2ZWwoYXMuZmFjdG9yKGVuY3Vlc3RhX3NhbHVkX3Rlc3QkY29uc3Vtb19zZW1hbmFsX3NuYWNrcyksIHJlZiA9IDgpCmxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nID0gbWFwKC54ID0gbW9kZWxzLCAuZiA9IGF1Z21lbnQsIG5ld2RhdGEgPSBlbmN1ZXN0YV9zYWx1ZF90ZXN0KSAKYGBgCgpDYWxjdWxhbW9zIGVsIFJNU0UgcGFyYSB0b2RvcyBsb3MgbW9kZWxvcyBhIGV2YWx1YXIgY29uIGxhcyBwcmVkaWNjaW9uZXMgZW4gdHJhaW5pbmcKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIE9idGVuZW1vcyBlbCBSTVNFIHBhcmEgbG9zIDQgbW9kZWxvcwptYXBfZGZyKC54ID0gbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3RpbmcsIC5mID0gcm1zZSwgdHJ1dGggPSBwZXNvLCBlc3RpbWF0ZSA9IC5maXR0ZWQsIC5pZD0ibW9kZWxvIikgJT4lIGFycmFuZ2UoLmVzdGltYXRlKQpgYGAKCkNhbGN1bGFtb3MgZWwgTUFFIHBhcmEgdG9kb3MgbG9zIG1vZGVsb3MgYSBldmFsdWFyIGNvbiBsYXMgcHJlZGljY2lvbmVzIGVuIHRyYWluaW5nCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbWFwX2RmcigueCA9IGxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nLCAuZiA9IG1hZSwgdHJ1dGggPSBwZXNvLCBlc3RpbWF0ZSA9IC5maXR0ZWQsIC5pZD0ibW9kZWxvIikgJT4lIGFycmFuZ2UoLmVzdGltYXRlKQpgYGAKCkNvbmNsdWltb3Mgc29icmUgZWwgZGF0YXNldCBkZSB0ZXN0aW5nOgoKLSBUYW50byBlbCBtb2RlbG8gbW9kZWxvX2xuXzJfciBjb21vIGVsIG1vZGVsb19sbl8zX3Igc29uIGxvcyBxdWUgbWVqb3IgUk1TRSB0aWVuZW4gY29tbyBlbiBsYSBtZWRpY2nDs24gc29icmUgdHJhaW5pbmcgY29uIDEwLjIKLSBUYW50byBlbCBtb2RlbG8gbW9kZWxvX2xuXzJfciBjb21vIGVsIG1vZGVsb19sbl8zX3Igc29uIGxvcyBxdWUgbWVqb3IgTUFFIHRpZW5lbiBjb21vIGVuIGxhIG1lZGljacOzbiBzb2JyZSB0cmFpbmluZyBjb24gNy41NgoKPCEtLSA1KSBEaWFnbsOzc3RpY28gZGVsIG1vZGVsbwpBbmFsaXphciBlbiBwcm9mdW5kaWRhZCBlbCBjdW1wbGltaWVudG8gZGUgbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvIGxpbmVhbCBwYXJhIGVsIG1vZGVsbyBpbmljaWFsLiAtLT4KCgojIyBEaWFnbsOzc3RpY28gZGVsIG1vZGVsbwoKUmVhbGl6YXJlbW9zIGRpZmVyZW50ZXMgcHJ1ZWJhcyBwYXJhIHZhbGlkYXIgZWwgY3VtcGxpbWllbnRvIG8gbm8gZGUgbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvIGxpbmVhbCwgZXN0b3MgY29uc2lzdGVuIGRlOiDOtWniiLxOKDAsz4MyKSBpbmRlcGVuZGllbnRlcyBlbnRyZSBzw60uCkxvcyBlcnJvcmVzIHNvbiBpbm9ic2VydmFibGVzLCBwb3IgbG8gdGFudG8gdGVuZHJlbW9zIHF1ZSB0cmFiYWphciBjb24gc3UgY29ycmVsYXRvIGVtcMOtcmljbzogbG9zIHJlc2lkdW9zIGVuIGxhcyB0w6ljbmljYXMgZGUgZGlhZ27Ds3N0aWNvLgoKUHJvY2VkZW1vcyBhIGdyYWZpY2FyIGVsIGNvbXBvcnRhbWllbnRvIGRlIGxvcyByZXNpZHVvcyBkZWwgbW9kZWxvCgoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQphdV9tb2RlbG9zID0gbWFwX2RmKG1vZGVscywgYXVnbWVudCwgLmlkID0gIm1vZGVsIikKCmcxID0gZ2dwbG90KGF1X21vZGVsb3MgJT4lIGZpbHRlcihtb2RlbCA9PSAibW9kZWxvX2xuX2Jhc2UiKSwgCiAgICAgICBhZXMoLmZpdHRlZCwgLnJlc2lkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zIE1PREVMTyBCQVNFIikgKyAKICB0aGVtZV9idygpCgpnMiA9IGdncGxvdChhdV9tb2RlbG9zICU+JSBmaWx0ZXIobW9kZWwgPT0gIm1vZGVsb19sbl9iYXNlIiksIAogICAgICAgYWVzKHNhbXBsZSA9IC5zdGQucmVzaWQpKSArCiAgc3RhdF9xcSgpICsKICBnZW9tX2FibGluZSgpICsKICBsYWJzKHRpdGxlID0gIk5vcm1hbCBRUSBwbG90IE1PREVMTyBCQVNFIikgKyAKICB0aGVtZV9idygpCgpnMyA9IGdncGxvdChhdV9tb2RlbG9zICU+JSBmaWx0ZXIobW9kZWwgPT0gIm1vZGVsb19sbl9iYXNlIiksIAogICAgICAgYWVzKC5maXR0ZWQsIHNxcnQoYWJzKC5zdGQucmVzaWQpKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZSA9ICJTY2FsZS1sb2NhdGlvbiBwbG90IE1PREVMTyBCQVNFIikKCmc0ID0gZ2dwbG90KGF1X21vZGVsb3MgJT4lIGZpbHRlcihtb2RlbCA9PSAibW9kZWxvX2xuX2Jhc2UiKSwgCiAgICAgICBhZXMoLmhhdCwgLnN0ZC5yZXNpZCkpICsKICBnZW9tX3ZsaW5lKHNpemUgPSAyLCBjb2xvdXIgPSAid2hpdGUiLCB4aW50ZXJjZXB0ID0gMCkgKwogIGdlb21faGxpbmUoc2l6ZSA9IDIsIGNvbG91ciA9ICJ3aGl0ZSIsIHlpbnRlcmNlcHQgPSAwKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKyAKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFsIHZzIGxldmVyYWdlIE1PREVMTyBCQVNFIikKIyBncmFmaWNvIHRvZG9zIGp1bnRvcwpncmlkLmFycmFuZ2UoZzEsIGcyLCBnMywgZzQsIG5yb3cgPSAyKQoKCmBgYAoKQ29uY2x1c2lvbmVzOgoKLSBSZXNpZHVvcyB2cyB2YWxvcmVzIHByZWRpY2hvczogUGFyZWNlIG5vIGV4aXN0aXIgZXN0cnVjdHVyYSBlbiBsb3MgZGF0b3MsIGxhIHZhcmlhbnphIG5vIHBhcmVjZSBpbmNyZW1lbnRhcnNlIGNvbiBsb3MgdmFsb3JlcyBwcmVkaWNob3MsIHBvciBsbyBxdWUgKipzYXRpc2ZhY2UgZWwgc3VwdWVzdG8gZGUgaG9tb2NlZGFzdGljaWRhZCoqLgotIE5vcm1hbCBRUSBwbG90OiBUYW50byBlbCBleHRyZW1vIHN1cGVyaW9yIGRlcmVjaG8gZW4gcGFydGljdWxhciwgY29tbyBlbCBpenF1aWVyZG8gaW5mZXJpb3Igbm8gc2UgYWp1c3RhbiBhIGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2EsICoqbm8gc2lndWVuIHVuYSBkaXN0cmlidWNpw7NuIG5vcm1hbCoqCi0gUmVzaWR1YWwgdnMgbGV2ZXJhZ2U6ICoqRXhpc3RlbiBhbGd1bm9zIHB1bnRvcyBkZSBhbHRvIGxldmVyYWdlKiosIGhhYnJpYSBxdWUgcmVhbGl6YXIgdW4gYW5hbGlzaXMgbWFzIHByb2Z1bmRvIHBhcmEgZW50ZW5kZXIgbGEgaW5mbHVlbmNpYSBkZSBlc3RvcyB2YWxvcmVzIHNvYnJlIGVsIG1vZGVsbywgZ3LDoWZpY2FtZW50ZSBubyBlcyBzaW1wbGUgZGV0ZXJtaW5hcmxvCgpEaWFnbsOzc3RpY28gZGVsIG1vZGVsbzogKipFbCBtb2RlbG8gY3JlYWRvIG5vIGN1bXBsZSBjb24gbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvIGxpbmVhbCoqLiBQYXJlY2VuIGV4aXN0aXIgcHJvYmxlbWFzIGRlIGZhbHRhIGRlIG5vcm1hbGlkYWQgeSBwcmVzZW5jaWEgZGUgb2JzZXJ2YWNpb25lcyBkZSBhbHRvIGxldmVyYWdlLgoKCjwhLS0gNikgTW9kZWxvIFJvYnVzdG8KTGVlciBlbCBhcmNoaXZvIOKAnGVuY3Vlc3RhX3NhbHVkX21vZGVsbzYuY3N24oCdLiBFc3RlIMO6bHRpbW8gY29uc2lzdGUgZW4gZWwgZGF0YXNldCBvcmlnaW5hbCBkZSB0cmFpbiBjb24gbGEKaW5jb3Jwb3JhY2nDs24gZGUgYWxndW5hcyBvYnNlcnZhY2lvbmVzIGFkaWNpb25hbGVzIHF1ZSBwdWVkZW4gaW5jbHVpciB2YWxvcmVzIGF0w61waWNvcy4gRW4gcGFydGljdWxhciwgb2JzZXJ2YXIKbGEgcmVsYWNpw7NuIGVudHJlIHBlc28geSBhbHR1cmEgwr9RdcOpIG9jdXJyZSBjb24gZXN0b3MgbnVldm9zIGRhdG9zPwpFbnRyZW5hciBlbCBtb2RlbG8gaW5pY2lhbCBjb24gZXN0b3MgbnVldm9zIGRhdG9zIHkgY29tZW50YXIgcXXDqSBzZSBvYnNlcnZhIGVuIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zIHkKbGFzIG3DqXRyaWNhcyBkZSBldmFsdWFjacOzbiAoUiBjdWFkcmFkbyBhanVzdGFkbywgUk1TRSB5IE1BRSkgcmVzcGVjdG8gYWwgbW9kZWxvIGVudHJlbmFkbyBjb24gZWwgc2V0IGRlCmVudHJlbmFtaWVudG8gb3JpZ2luYWwuCkVudHJlbmFyIHVuIG1vZGVsbyByb2J1c3RvIGNvbiBsYSBtaXNtYSBlc3BlY2lmaWNhY2nDs24gcXVlIGVsIG1vZGVsbyBpbmljaWFsIHNvYnJlIGxvcyBudWV2b3MgZGF0b3MuCkNvbXBhcmFyIGxvcyBjb2VmaWNpZW50ZXMgeSBzdSBwZXJmb3JtYW5jZSAoUk1TRSB5IE1BRSkgcmVzcGVjdG8gYWwgbW9kZWxvIGluaWNpYWwgbm8gcm9idXN0byBlbnRyZW5hZG8KZW4gZXN0ZSBwdW50by4gwr9RdcOpIHB1ZWRlIGNvbmNsdWlyIGFsIHJlc3BlY3RvPwpOb3RhOiBsb3MgcmVnaXN0cm9zIHF1ZSBzZSBzdW1hbiBlbiBlc3RlIHB1bnRvIHNvbiBvYnNlcnZhY2lvbmVzIGZpY3RpY2lhcyBxdWUgc2UgZ2VuZXJhcm9uIGEgcGFydGlyIGRlCm9ic2VydmFjaW9uZXMgcmVhbGVzIGRlbCBzZXQgZGUgZGF0b3Mgb3JpZ2luYWwuIC0tPgoKIyMgTW9kZWxvIHJvYnVzdG8KCgojIyMgQW7DoWxpc2lzIGRlbCBkYXRhc2V0CgpMZWVtb3MgZWwgbnVldm8gYXJjaGl2byBjb24gbGEgaW5jb3Jwb3JhY2nDs24gZGUgYWxndW5hcyBvYnNlcnZhY2lvbmVzIGFkaWNpb25hbGVzIHF1ZSBwdWVkZW4gaW5jbHVpciB2YWxvcmVzIGF0w61waWNvcwoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpsaWJyYXJ5KE1BU1MpCgplbmN1ZXN0YV9zYWx1ZF90cmFpbl9vcmlnaW5hbCA9IHJlYWRfY3N2KCIvVXNlcnMvZGZvbnRlbmxhL01hZXN0cmlhLzIwMjJDMi9FRUEvcHJhY3RpY2EvcmVwby9FRUEtMjAyMi9UUDEvZW5jdWVzdGFfc2FsdWRfbW9kZWxvNi5jc3YiKSAlPiUgbXV0YXRlKGlkID0gMTpucm93KC4pKSAKZW5jdWVzdGFfc2FsdWRfdHJhaW5fb3JpZ2luYWwgJT4lIGdsaW1wc2UoKQpgYGAKCkdyYWZpY2Ftb3MgbGEgcmVsYWNpw7NuIHBlc28gLyBhbHR1cmEgcGFyYSBvYnNlcnZhciBsYXMgYW5vbWFsaWFzIGluc2VydGFkYXMgZW4gZWwgbnVldm8gZGF0YXNldApgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CgplbmN1ZXN0YV9zYWx1ZF90cmFpbl9vcmlnaW5hbCAlPiUgZ2dwbG90KC4sIGFlcyh4ID0gYWx0dXJhLCB5ID0gcGVzbykpICsgCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsgI2NhcGEgZGUgbG9zIGRhdG9zCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZT0iTW9kZWxvIExpbmVhbCBNw7psdGlwbGU6IEFsdHVyYSB4IFBlc28iLCB4PSJBbHR1cmEiLCB5PSJQZXNvIikgCmBgYAoKT2JzZXJ2YW1vcyBxdWUgZXhpc3RlbiB1bmEgY2FudGlkYWQgZGUgcHVudG9zIHJlbGV2YW50ZXMgcXVlIHNlIGFsZWphbiBkZSBmb3JtYSBjYXRlZ8OzcmljYSBkZSBsYSBkaXN0cmlidWNpw7NuIGdlbmVyYWwsIHVuYSBpbXBvcnRhbnRlIGNhbnRpZGFkIGRlIG91dGxpZXJzCgoKTm9zIHF1ZWRhbW9zIGNvbiBsYXMgdmFyaWFibGVzIGRlIGludGVyw6lzIHBhcmEgcmVwcm9kdWNpciBlbCBtb2RlbG8gYmFzZQoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQoKZW5jdWVzdGFfc2FsdWRfdHJhaW5fb3JpZ2luYWxfYmFzZSA9IGVuY3Vlc3RhX3NhbHVkX3RyYWluX29yaWdpbmFsICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNlbGVjY2lvbmFtb3MgbGFzIHZhcmlhYmxlcyBkZSBpbnRlcsOpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGVkYWQsIGdlbmVybywgYWx0dXJhLCBwZXNvLGRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsLGNvbnN1bW9fZGlhcmlvX2FsY29ob2wpCgplbmN1ZXN0YV9zYWx1ZF90cmFpbl9vcmlnaW5hbF9iYXNlICU+JSBnbGltcHNlCgpgYGAKCkdyYWZpY2Ftb3MgbGFzIHJlbGFjaW9uZXMgZW50cmUgdmFyaWFibGVzIGRlbCBudWV2byBkYXRhc2V0CgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CgplbmN1ZXN0YV9zYWx1ZF90cmFpbl9vcmlnaW5hbF9iYXNlICU+JSBnZ3BhaXJzKGFlcyhjb2xvcj1nZW5lcm8pLCB1cHBlciA9IGxpc3QoY29udGludW91cyA9IHdyYXAoImNvciIsIHNpemUgPSAzLCBoanVzdD0wLjgsIGFsaWduUGVyY2VudD0wLjE1KSksIGxlZ2VuZCA9IDI1KSArIAogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0wLjUpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKCmBgYAoKUmVwcmVzZW50YW1vcyBsYSBjb3JyZWxhY2nDs24gZGUgUGVhcnNvbiBlbnRyZSB2YXJpYWJsZXMKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KZW5jdWVzdGFfc2FsdWRfdHJhaW5fb3JpZ2luYWxfYmFzZSAlPiUgCiBjb3JyZWxhdGUoKSAlPiUgIyBjb252aWVydGUgbGEgbWF0cml6IGRlIGNvcnIgZW4gZGF0YWZyYW1lCiAgc2hhdmUoKSAlPiUgIyBzb2xvIG11ZXN0cmEgaW5mb3JtYWNpw7NuIGRlYmFqbyBkZSBsYSBkaWFnb25hbCBwcmluY2lwYWwKICBmYXNoaW9uKCkgIyBhY29tb2RhIGxvcyBkYXRvcyBlbiBmb3JtYSB0aWR5IChwb3IgZWouIHJlZG9uZGVvIGRlIGRlY2ltYWxlcykKYGBgCgpQb2RlbW9zIG9ic2VydmFyIHF1ZSBsYSBjb3JyZWxhY2nDs24gZW50cmUgcGVzbyB5IGFsdHVyYSBlbiByZWxhY2nDs24gYWwgZGF0YXNldCBiYXNlLCBkaXNtaW51ecOzIGNvbnNpZGVyYWJsZW1lbnRlIHBhc2FuZG8gZGUgdW4gMC41IGEgdW4gMC4yOSwgZ3LDoWZpY2FtZW50ZQpzZSB2aXN1YWxpemEgdW5hIGNsYXJhIGNvcnJlc3BvbmRlbmNpYSBlbnRyZSBlc3RvcyB2YWxvcmVzLCBhaG9yYSBiaWVuICoqcGFyYSBlbCBudWV2byBkYXRhc2V0IGV4aXN0ZSB1bmEgY2FudGlkYWQgZGUgZGF0b3Mgb3V0bGllcnMgc2lnbmlmaWNhdGl2b3MqKgoKCiMjIyBDcmVhY2nDs24gZGVsIG1vZGVsbwoKQWp1c3RhbW9zIGVsIGRhdGFzZXQgYSBudWVzdHJvIG1vZGVsbyBsaW5lYWwgbcO6bHRpcGxlIGJhc2UgKHBlc28gfiBlZGFkICsgZ2VuZXJvICsgYWx0dXJhICsgZGlhc19hY3RpdmlkYWRfZmlzaWNhX3NlbWFuYWwgKyBjb25zdW1vX2RpYXJpb19hbGNvaG9sKQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CiMgYWp1c3RhbW9zIG1vZGVsbyBsaW5lYWwgbcO6bHRpcGxlCm1vZGVsb19kc19vcmlnaW5hbF9sbl9iYXNlIDwtIGxtKHBlc28gfiBlZGFkICsgZ2VuZXJvICsgYWx0dXJhICsgZGlhc19hY3RpdmlkYWRfZmlzaWNhX3NlbWFuYWwgKyBjb25zdW1vX2RpYXJpb19hbGNvaG9sLCBkYXRhID0gZW5jdWVzdGFfc2FsdWRfdHJhaW5fb3JpZ2luYWxfYmFzZSkKIyBSZXN1bWVuIGRlbCBtb2RlbG8KdGlkeV9sbl9kc19vcmlnaW5hbF9iYXNlIDwtIHRpZHkobW9kZWxvX2RzX29yaWdpbmFsX2xuX2Jhc2UsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9sbl9kc19vcmlnaW5hbF9iYXNlCmBgYAoKUmVhbGl6YW1vcyB1biBkaWFnbsOzc3RpY28gZGVsIG1vZGVsbyBhIHRyYXbDqXMgZGUgbG9zIHJlc2lkdW9zCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KcGxvdChtb2RlbG9fZHNfb3JpZ2luYWxfbG5fYmFzZSkKYGBgCgpWZW1vcyBsYSBzaWduaWZpY2FuY2lhIGRlIGxvcyBhdHJpYnV0b3MKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0Kb3B0aW9ucygic2NpcGVuIj0xKQp0aWR5X2xuX2RzX29yaWdpbmFsX2Jhc2UgJT4lCiAgZHBseXI6OnNlbGVjdCh0ZXJtLCBzdGF0aXN0aWMsIHAudmFsdWUsIGNvbmYubG93LCBjb25mLmhpZ2gpCmBgYAoKQW5hbGl6YW1vcyBsYSB2YXJpYWJpbGlkYWQgZXhwbGljYXRpdmEgZGVsIG1vZGVsbwpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmdsYW5jZShtb2RlbG9fZHNfb3JpZ2luYWxfbG5fYmFzZSkKYGBgCgpPYnNlcnZhbW9zIHF1ZSBsYSB2YXJpYWJpbGlkYWQgcG9yY2VudHVhbCBleHBsaWNhZGEgcG9yIFItY3VhZHJhZG8geSBSLWN1YWRyYWRvIGFqdXN0YWRvIGRpc21pbnV5ZSBjb25zaWRlcmFibGVtZW50ZSBlbiByZWxhY2nDs24gYWwgbWlzbW8gbW9kZWxvIGVudHJlbmFkbyBlbiBlbCBkYXRhc2V0IHNpbiBvdXRsaWVycwoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQp0aWR5KGFub3ZhKG1vZGVsb19kc19vcmlnaW5hbF9sbl9iYXNlKSkKYGBgCgojIyMgRXZhbHVhY2nDs24gY29tcGFyYXRpdmEgZGVsIG1vZGVsbyBiYXNlIGNvbiBlbCBudWV2byBkYXRhc2V0IHkgZWwgb3JpZ2luYWwKCkFncnVwYW1vcyBsb3MgbW9kZWxvcyBwYXJhIHBvZGVyIGNvbXBhcmFybG9zCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KIyBhcm1hbW9zIGxpc3RhIGNvbiB0b2RvcyBsb3MgbW9kZWxvcwptb2RlbHNfbGV2ZXJhZ2UgPC0gbGlzdChtb2RlbG9fZHNfb3JpZ2luYWxfbG5fYmFzZSA9IG1vZGVsb19kc19vcmlnaW5hbF9sbl9iYXNlLCBtb2RlbG9fbG5fYmFzZSA9IG1vZGVsb19sbl9iYXNlKQojIGNhbGN1bGFtb3MgbGFzIHZhcmlhYmxlcyByZXN1bWVuCm1hcF9kZihtb2RlbHNfbGV2ZXJhZ2UsIHRpZHksIC5pZCA9ICJtb2RlbCIpCmBgYAoKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpsaXN0YV9wcmVkaWNjaW9uZXNfdHJhaW5pbmdfbGV2ZXJhZ2UgPSBtYXAoLnggPSBtb2RlbHNfbGV2ZXJhZ2UsIC5mID0gYXVnbWVudCkgCmBgYAoKVXRpbGl6YW1vcyBsYSBmdW5jacOzbiBnbGFuY2Ugc29icmUgdG9kb3MgbG9zIG1vZGVsb3MgcGFyYSBjb21wYXJhciBsYSB2YXJpYWJpbGlkYWQgZGUgbG9zIG1pc21vcwpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmRmX2V2YWx1YWNpb25fdHJhaW5fbGV2ZXJhZ2UgPSBtYXBfZGYobW9kZWxzX2xldmVyYWdlLCBnbGFuY2UsIC5pZCA9ICJtb2RlbCIpICU+JQogICMgb3JkZW5hbW9zIHBvciBSMiBhanVzdGFkbwogIGFycmFuZ2UoZGVzYyhhZGouci5zcXVhcmVkKSkKZGZfZXZhbHVhY2lvbl90cmFpbl9sZXZlcmFnZQpgYGAKCgpDYWxjdWxhbW9zIGVsIFJNU0UgcGFyYSB0b2RvcyBsb3MgbW9kZWxvcyBhIGV2YWx1YXIKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQptYXBfZGZyKC54ID0gbGlzdGFfcHJlZGljY2lvbmVzX3RyYWluaW5nX2xldmVyYWdlLCAuZiA9IHJtc2UsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSBhcnJhbmdlKC5lc3RpbWF0ZSkKYGBgCgpDYWxjdWxhbW9zIGVsIE1BRSBwYXJhIHRvZG9zIGxvcyBtb2RlbG9zIGEgZXZhbHVhcgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9Cm1hcF9kZnIoLnggPSBsaXN0YV9wcmVkaWNjaW9uZXNfdHJhaW5pbmdfbGV2ZXJhZ2UsIC5mID0gbWFlLCB0cnV0aCA9IHBlc28sIGVzdGltYXRlID0gLmZpdHRlZCwgLmlkPSJtb2RlbG8iKSAlPiUgYXJyYW5nZSguZXN0aW1hdGUpCmBgYAoKUmVhbGl6YW5kbyBsYSBjb21wYXJhY2nDs24gZGUgbGFzIG1ldHJpY2FzIFItY3VhZHJhZG8gYWp1c3RhZG8sIFJNU0UgeSBNQUUsIG9ic2VydmFtb3MgcXVlOgoKLSBFbiBsYSBldmFsdWFjacOzbiBkZWwgbnVldm8gZGF0YXNldCBjb24gb3V0bGllcnMsIGVsIFItY3VhZHJhZG8gYWp1c3RhZG8gZGlzbWludXnDsyBzaWduaWZpY2F0aXZhbWVudGUgcG9yIGxvIHF1ZSBudWVzdHJvIG1vZGVsbyAKcmVwcmVzZW50YSB1bmEgdmFyaWFiaWxpZGFkIGRlIGxvcyBkYXRvcyBtZW5vciAqKihtb2RlbG9fbG5fYmFzZTowLjM1NCAtICBtb2RlbG9fZHNfb3JpZ2luYWxfbG5fYmFzZTowLjEwOSkqKgotIEVsIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIGF1bWVudGEgZGUgZm9ybWEgY29uc2lkZXJhYmxlICoqKG1vZGVsb19sbl9iYXNlOjkuOTEgLSAgbW9kZWxvX2RzX29yaWdpbmFsX2xuX2Jhc2U6MTUuNykqKgotIEVsIGVycm9yIGFic29sdXRvIE1BRSwgdGFtYmnDqW4gY3JlY2UgZGUgZm9ybWEgY29zaWRlcmFibGUgICoqKG1vZGVsb19sbl9iYXNlOjcuNDYgLSAgbW9kZWxvX2RzX29yaWdpbmFsX2xuX2Jhc2U6OS4yMykqKgoKQ29uY2x1aW1vcyBjb24gbG8gb2JzZXJ2YWRvIHF1ZSBudWVzdHJvIG1vZGVsbyBlcyBzZW5zaWJsZSBhIG91dGxpZXJzLCB5IGFudGUgbGEgcHJlY2VuY2lhIGRlIGVzdG9zLCBudWVzdHJhcyBtw6l0cmljYXMgZGUgcGVyZm9ybWFuY2UgZGVsIG1vZGVsbyBlbXBlb3JhbiBkcsOhc3RpY2FtZW50ZQoKIyMjIENyZWFjacOzbiBtb2RlbG8gbGluZWFyIHJvYnVzdG8KCkFudGUgbGEgYXByZWNpYWNpw7NuIGRlIGNvbW8gZW1wZW9yYSBudWVzdHJvIG1vZGVsbyBhbnRlIGxhIGV4aXN0ZW5jaWEgZGUgb3V0bGllcnMgZW4gbnVlc3RybyBkYXRhc2V0LCBwcm9jZWRlbW9zIGEgcmVhbGl6YXIgdW4gZW50cmVuYW1pZW50byBjb24gdW4gbW9kZWxvIGxpbmVhbApyb2J1c3RvIGVsIGN1YWwgZXNwZXJhcmVtb3MgcXVlIGJyaW5kZSBtZWpvcmVzIHJlc3VsdGFkb3MgZGViaWRvIGEgbGEgcG9uZGVyYWNpw7NuIHF1ZSByZWFsaXphbiBzb2JyZSBsYSBpbmZsdWVuY2lhIGRlIGxvcyBjYXNvcyBhdMOtcGljb3MKYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIGFqdXN0YW1vcyBtb2RlbG8gbGluZWFsIG3Dumx0aXBsZQptb2RlbG9fZHNfb3JpZ2luYWxfbG5fcm9idXN0X2Jhc2UgPC0gcmxtKHBlc28gfiBlZGFkICsgZ2VuZXJvICsgYWx0dXJhICsgZGlhc19hY3RpdmlkYWRfZmlzaWNhX3NlbWFuYWwgKyBjb25zdW1vX2RpYXJpb19hbGNvaG9sLCBkYXRhID0gZW5jdWVzdGFfc2FsdWRfdHJhaW5fb3JpZ2luYWxfYmFzZSkKIyAjIFJlc3VtZW4gZGVsIG1vZGVsbwpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KdGlkeShtb2RlbG9fZHNfb3JpZ2luYWxfbG5fYmFzZSkKYGBgCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CnRpZHkobW9kZWxvX2RzX29yaWdpbmFsX2xuX3JvYnVzdF9iYXNlKQpgYGAKCkNvbXBhcmFuZG8gY29lZmljaWVudGVzLCBwb2RlbW9zIHZlciBjb21vIGxvcyByZWxhY2lvbmFkb3MgYWwgbW9kZWxvIHJvYnVzdG8sIG5vIHN1ZnJlbiBncmFuZGVzIG1vZGlmaWNhY2lvbmVzIGVuIHJlbGFjacOzbiBhbCBtb2RlbG8gZW50cmVuYWRvIGNvbiBlbCBkYXRhc2V0CnNpbiBvdXRsaWVycywgbWllbnRyYXMgcXVlIGVuIGVsIG1vZGVsbyBsaW5lYWwgY29tw7puIGVudHJlbmFkbyBjb24gZWwgZGF0YXNldCBjb24gb3V0bGllcnMsIGxvcyBjb2VmaWNpZW50ZXMgY2FtYmlhbiBjb25zaWRlcmFibGVtZW50ZSBkZWJpZG8gYSBlc3RvcyBkYXRvcy4KCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0Kb3B0aW9ucygic2NpcGVuIj0xKQp0aWR5X2xuX2RzX29yaWdpbmFsX3JvYnVzdCA8LSB0aWR5KG1vZGVsb19kc19vcmlnaW5hbF9sbl9yb2J1c3RfYmFzZSwgY29uZi5pbnQgPSBUUlVFKQp0aWR5X2xuX2RzX29yaWdpbmFsX3JvYnVzdCAlPiUgZHBseXI6OnNlbGVjdCh0ZXJtLCBzdGF0aXN0aWMsIGNvbmYubG93LCBjb25mLmhpZ2gpCgpgYGAKCiMjIyBFdmFsdWFjacOzbiBjb21wYXJhdGl2YSBkZWwgbW9kZWxvIGNvbcO6biB5IHJvYnVzdG8KCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbW9kZWxzX3JvYnVzdCA8LSBsaXN0KG1vZGVsb19sbl81X3JvYnVzdCA9IG1vZGVsb19kc19vcmlnaW5hbF9sbl9yb2J1c3RfYmFzZSwgbW9kZWxvX2xuXzVfYmFzZSA9IG1vZGVsb19kc19vcmlnaW5hbF9sbl9iYXNlKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0Kcm9idXN0X3ByZWRpY3Rpb25zX292ZXJfdHJhaW5pbmcgPSBtYXAoLnggPSBtb2RlbHNfcm9idXN0LCAuZiA9IGF1Z21lbnQpIAptYXBfZGZyKC54ID0gcm9idXN0X3ByZWRpY3Rpb25zX292ZXJfdHJhaW5pbmcsIC5mID0gcm1zZSwgdHJ1dGggPSBwZXNvLCBlc3RpbWF0ZSA9IC5maXR0ZWQsIC5pZD0ibW9kZWxvIikgJT4lIGFycmFuZ2UoLmVzdGltYXRlKQptYXBfZGZyKC54ID0gcm9idXN0X3ByZWRpY3Rpb25zX292ZXJfdHJhaW5pbmcsIC5mID0gbWFlLCB0cnV0aCA9IHBlc28sIGVzdGltYXRlID0gLmZpdHRlZCwgLmlkPSJtb2RlbG8iKSAlPiUgYXJyYW5nZSguZXN0aW1hdGUpCgpgYGAKCi0gT2JzZXJ2YW1vcyBxdWUgZWwgUk1TRSBubyBtZWpvcmEgY29uIGVsIG1vZGVsbyByb2J1c3RvICgxNi4wKQotIE5vdGFtb3MgdW5hIG1lam9yYSBpbXBvcnRhbnRlIGVuIGVsIGVycm9yIGFic29sdXRvIE1BRSwgZW50ZXIgZWwgbW9kZWxvIGxpbmVhbCAoOS4yMykgeSByb2J1c3RvICg4Ljc2KQoKRXZhbHVhbW9zIGxhIHBlcmZvcm1hbmNlIGRlIGxvcyBtb2RlbG9zIHNvYnJlIGVsIGNvbmp1bnRvIGRlIHRlc3RpbmcKCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KIyBBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYXVnbWVudCBhIGxvcyA0IG1vZGVsb3MgY29uIGVsIHNldCBkZSB0ZXN0aW5nCmxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nX3JvYnVzdCA9IG1hcCgueCA9IG1vZGVsc19yb2J1c3QsIC5mID0gYXVnbWVudCwgbmV3ZGF0YSA9IGVuY3Vlc3RhX3NhbHVkX3Rlc3QpIAojIE9idGVuZW1vcyBlbCBSTVNFIHBhcmEgbG9zIDQgbW9kZWxvcwptYXBfZGZyKC54ID0gbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3Rpbmdfcm9idXN0LCAuZiA9IHJtc2UsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSBhcnJhbmdlKC5lc3RpbWF0ZSkKbWFwX2RmcigueCA9IGxpc3RhX3ByZWRpY2Npb25lc190ZXN0aW5nX3JvYnVzdCwgLmYgPSBtYWUsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSBhcnJhbmdlKC5lc3RpbWF0ZSkKCmBgYAoKRXZhbHVhbmRvIGxvcyBtb2RlbG9zIHNvYnJlIGVsIGNvbmp1bnRvIGRlIHBydWViYXMKCi0gRWwgZXJyb3IgY3VhZHLDoXRpY28gbWVkaW8gZGlzbWludXllIGNvbiBlbCBtb2RlbG8gcm9idXN0byAoMTAuMykgZW4gY29tcGFyYWNpb24gY29uIGVsIGxpbmVhbCAoMTAuNikgcGFyYSBlbCBkYXRhc2V0IGNvbiBvdXRsaWVycywgcGVybyBubyBkZSBmb3JtYSBzaWduaWZpY2F0aXZhLCBubyBlcyB1bmEgbcOpdHJpY2Egcm9idXN0YSwgc2Vuc2libGUgYSBvdXRsaWVycwotIEVsIGVycm9yIGFic29sdXRvIHNpIGRpc21pbnV5ZSBjb24gZWwgbW9kZWxvIHJvYnVzdG8gKDcuNTMpIGVuIGNvbXBhcmFjacOzbiBjb24gZWwgbGluZWFsICg4LjE1KSBkZSBmb3JtYSBzaWduaWZpY2F0aXZhIHBhcmEgZWwgZGF0YXNldCBjb24gb3V0bGllcnMsIGRhbmRvIHVuIHZhbG9yIHNpbWlsYXIgYSBsYSBtZWRpY2nDs24gZGVsIG1vZGVsbwpzaW4gbGEgcHJlc2VuY2lhIGRlIGxvcyB2YWxvcmVzIGF0w61waWNvcyAKCkNvbmNsdXNpw7NuLCBhbnRlIGxhIHByZXNlbmNpYSBkZSB1bmEgY2FudGlkYWQgc2lnbmlmaWNhdGl2YSBkZSBvdXRsaWVycywgdXRpbGl6YXIgdW4gbW9kZWxvIGxpbmVhbCByb2J1c3RvIG5vcyBzaXJ2aW8gcGFyYSBwb2RlciB0ZW5lciB1biBtb2RlbG8gcXVlIGFudGUgCmxhIHByZXNlbmNpYSBkZSBlc3RvcyB2YWxvcmVzIGF0w61waWNvcyBzZSBzaWd1ZSBjb21wb3J0YW5kbyBkZSB1bmEgbWFuZXJhIHNpbWlsYXIgYWwgbW9kZWxvIHF1ZSBmdWUgZW50cmVuYWRvIHNpbiBvdXRsaWVycywgbWllbnRyYXMgcXVlIGVsIG1vZGVsbyBsaW5lYWwgCm5vIHJvYnVzdG8sIHNpZW5kbyBlbnRyZW5hZG8gZW4gdW4gZGF0YXNldCBjb24gb3V0bGllcnMsIGNhbWJpYSBzaWduaWZpY2F0aXZhbWVudGUgc3VzIGNvZWZpY2llbnRlcy4gCkhhY2Ugc2VudGlkbyBwb25lciBmb2NvIHNvYnJlIGxhIG3DqXRyaWEgTUFFIHlhIHF1ZSBlcyBtw6FzIHJvYnVzdG8gY3VhbmRvIGxvcyBkYXRvcyB0aWVuZW4gb3V0bGllcnMgbyBkYXRvcyBhdMOtcGljb3MgeSBlcyBsYSBtZWpvciBvcGNpw7NuIGEgdXNhciBlbiBlc29zIGNhc29zCgojIyMjIENvbnNpZGVyYWNpb25lcwpUb2RvcyBsb3MgZnJhZ21lbnRvcyBkZSBjw7NkaWdvcyB1dGlsaXphZG9zIGVuIGVsIHRyYWJham8gcHLDoWN0aWNvIGZ1ZXJvbiBvcmllbnRhZG8gYSBwYXJ0aXIgZGUgbm90ZWJvb2tzIHByb3B1ZXN0b3MgcG9yIGxhIGPDoXRlZHJhCkVuIGN1YW50byBhbCBtb2RlbG8gcm9idXN0byBkb25kZSB1dGlsaWNlIGxhIGxpYnJlcsOtYSBNQUFTLCBhbGd1bmFzIGZ1ZW50ZXMgZGUgZWplbXBsbyBmdWVyb24gW2xpbmsxXShodHRwczovL3N0YXRzLm9hcmMudWNsYS5lZHUvci9kYWUvcm9idXN0LXJlZ3Jlc3Npb24vKSwgW2xpbmsyXShodHRwczovL3d3dy5zdGF0b2xvZ3kub3JnL3JvYnVzdC1yZWdyZXNzaW9uLWluLXIvKQ==